Что не так с тестированием объекта, чтобы увидеть, реализует ли он интерфейс? - программирование

Что не так с тестированием объекта, чтобы увидеть, реализует ли он интерфейс?

В комментариях этот ответ указано, что " проверяет, реализовал ли объект интерфейс, как бы это ни было, это плохая вещь"

Ниже я верю, что это пример этой практики:

public interface IFoo
{
   void Bar();
}

public void DoSomething(IEnumerable<object> things)
{
   foreach(var o in things)
   {
       if(o is IFoo)
           ((IFoo)o).Bar();
   }
}

С моим любопытством, задуманным как кто-то, кто использовал вариации этого шаблона раньше, я искал хороший пример или объяснение того, почему это плохо, и не смог найти его.

Хотя очень возможно, что я неправильно понял комментарий, может ли кто-нибудь предоставить мне пример или ссылку, чтобы лучше объяснить комментарий?

4b9b3361

Ответ 1

Это зависит от того, что вы пытаетесь сделать. Иногда это может быть целесообразно - примеры могут включать:

  • LINQ to Objects, где он использовался для оптимизации операций типа Count, которые могут выполняться более эффективно с помощью IList<T> через специализированные члены.
  • LINQ to XML, где он использовался для предоставления действительно дружественного API, который принимает широкий диапазон типов, итерации по значениям, где это необходимо.
  • Если вы хотите найти все элементы управления определенного типа под определенным элементом управления в Windows Forms, вам нужно будет проверить, был ли каждый элемент управления контейнером для определения того, следует ли рекурсивно или нет.

В других случаях это менее целесообразно, и вы должны подумать, можете ли вы вместо этого изменить тип параметра. Это определенно "запах" - обычно вам не следует беспокоиться о деталях реализации того, что было передано вам; вы должны просто использовать API, предоставляемый объявленным типом параметра. Это также известно как нарушение Принципа замены Лискова.

Независимо от того, что могут сказать догматические разработчики, бывают случаи, когда вы просто хотите проверить тип времени выполнения объекта. Трудно переопределить object.Equals(object) правильно, не используя is/as/GetType, например:) Это не всегда плохо, но это всегда должно заставлять вас подумать, есть ли лучший подход. Используйте экономно, только там, где это действительно самая подходящая конструкция.


Я бы скорее написал код, который вы показали так:

public void DoSomething(IEnumerable<object> things)
{
    foreach(var foo in things.OfType<IFoo>())
    {
        foo.Bar();
    }
}

Выполняет то же самое, но более аккуратно:)

Ответ 2

Я ожидаю, что метод будет выглядеть следующим образом: он выглядит намного безопаснее:

public void DoSomething(IEnumerable<IFoo> things)
{
   foreach(var o in things)
   {
           o.Bar();
   }
}

Чтобы узнать о названном нарушении принципа Лискова: Что такое принцип замены Лискова?

Ответ 3

Если вы хотите знать, почему комментатор сделал этот комментарий, вероятно, лучше попросить их объяснить.

Я бы не стал считать код, который вы опубликовали, "плохим". Более "подлинно" плохой практикой является использование интерфейсов в качестве маркеров. То есть вы не планируете фактически использовать метод интерфейса; скорее, вы объявили интерфейс в классе как способ его описания каким-то образом. Используйте атрибуты, а не интерфейсы, в качестве маркеров в классах.

Интерфейсы маркеров опасны несколькими способами. В реальной ситуации я когда-то столкнулся с тем, что важный продукт принял плохое решение на основе интерфейса маркера: http://blogs.msdn.com/b/ericlippert/archive/2004/04/05/108086.aspx

Тем не менее, сам компилятор С# использует "интерфейс маркера" в одной ситуации. Мэдс рассказывает историю здесь: http://blogs.msdn.com/b/madst/archive/2006/10/10/what-is-a-collection_3f00_.aspx

Ответ 4

Причина в том, что зависимость от этого интерфейса будет не сразу видна без копания в коде.

Ответ 5

Утверждение

проверка того, реализовал ли объект интерфейс, безудержный как бы то ни было, это плохая вещь

Слишком догматично, на мой взгляд. Как ответили другие люди, вы вполне можете передать коллекцию IFoo вашему методу и достичь того же результата.

Однако интерфейсы могут быть полезны для добавления дополнительных функций в классы. Например, инфраструктура .net обеспечивает IDataErrorInfo интерфейс *. Когда это реализовано, он указывает потребителю, что в дополнение к стандартной функциональности класса, он также может предоставлять информацию об ошибках.

В этом случае информация об ошибке является необязательной. Модель представления WPF может содержать или не предоставлять информацию об ошибке. Без запросов для интерфейсов эта дополнительная функция не была бы возможна без базовых классов с большой площадью поверхности.

* На данный момент мы проигнорируем ужасный дизайн интерфейса IDataErrorInfo.

Ответ 6

Если ваш метод требует, чтобы вы вводили экземпляр интерфейса, вы должны относиться к нему одинаково независимо от реализации.
В вашем примере у вас обычно не будет общего списка объектов, но список ISomething и вызов ISomething.Bar() будет реализован конкретным типом, поэтому вызовет его реализацию. Если эта реализация ничего не делает, вам не нужно делать чек.

Ответ 7

Мне не нравится весь этот стиль кодирования типа "включить тип" по нескольким причинам. (Примеры, сделанные в отношении моей индустрии, разработки игр. Извиняюсь заранее.:))

Прежде всего, я думаю, что это неряшливо, чтобы иметь разнородную коллекцию предметов. Например. Я мог бы иметь коллекцию "все повсюду", но затем, когда итерация коллекции для применения эффектов пули или повреждения огня или вражеского ИИ, я должен пройти этот список, который в основном относится к тому, что мне неинтересно. Это намного "чище" ИМХО иметь отдельные коллекции пуль, бушующие огни и врагов. Обратите внимание, что нет причин, по которым я не могу иметь один элемент в нескольких коллекциях; во всех трех этих списках можно было бы указать одну горящую роботизированную ракету, чтобы сделать части своего "обновления" соответствующим трем типам логики, которые необходимо выполнить. Вне того, что у меня есть "одна единственная коллекция, которая ссылается на все", я думаю, что коллекция, содержащая все повсюду, не очень полезна; вы ничего не можете сделать с чем-либо в списке, если вы не запросите его для того, что он может сделать.

Я ненавижу делать ненужную работу. Это действительно связано с вышесказанным, но когда вы создаете данную вещь, вы знаете, каковы ее возможности (или можно запросить их в этой точке), поэтому вы могли бы воспользоваться возможностью в то время, чтобы поместить их в нужные более конкретные коллекции, У вас есть 16 мс, чтобы обрабатывать все в мире, не хотите ли вы тратить свое время на то, чтобы заниматься, запрашивать и выбирать из общих вещей, или вы хотите заняться бизнесом и работать только с конкретными вещами, о которых вы заботитесь?

По моему опыту, преобразование кодовой базы из общей операции на гетерогенных наборах данных в систему с однородными наборами данных привело не только к повышению производительности, но и к повышению понимания, которое происходит из более простого кода, делающего более очевидную работу, и в целом уменьшение суммы кода, необходимого для выполнения любой заданной задачи.

Так что, догматично говорить, что запросы на интерфейсы плохие, но похоже, что это упрощает ситуацию, если вы можете понять, как избежать необходимости запрашивать что-либо. Что касается моих "показателей производительности" и счетчика, что "если вы не измеряете его, вы не можете ничего сказать об этом", должно быть очевидно, что не делать что-то быстрее, чем делать это. Независимо от того, важно ли это для отдельного проекта, программиста или функции, зависит от человека с редактором, но если я могу упростить код и, делая это, сделайте меньше работы для тех же результатов, я собираюсь сделать он не мешает измерять.

Ответ 8

Я не рассматриваю это как "плохую вещь" вообще, по крайней мере, не сам по себе. Код - это просто буквальная транскрипция "x все y в z", и в ситуации, когда вам нужно это сделать, это вполне приемлемо. Разумеется, вы можете использовать things.OfType<Foo>() для получения конкретизации.

Основная причина рекомендовать против него состоит в том, что в соответствии с теологией ООП интерфейсы предназначены для моделирования различных видов "черного ящика", для которых объект может быть заменен. Предотвращение алгоритма выполнения интерфейса представляет собой перемещение по алгоритму, который должен быть в этом интерфейсе.

По сути, интерфейс является поведенческой ролью. Если вы считаете, что ООП - хорошая идея, то вы должны использовать интерфейсы только для моделирования поведения, так что алгоритмы не должны. Я не думаю, что то, что проходит для ООП в наши дни, на самом деле хорошая идея, так что это насколько мой ответ может быть полезен.