Что плохого в тестировании объекта, чтобы увидеть, реализует ли он интерфейс? - PullRequest
30 голосов
/ 11 октября 2011

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

Ниже приведен пример этой практики:

public interface IFoo
{
   void Bar();
}

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

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

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

Ответы [ 8 ]

36 голосов
/ 11 октября 2011

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

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

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

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


Лично я бы лучше написал код, который вы показали, вот так:

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

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

18 голосов
/ 11 октября 2011

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

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

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

10 голосов
/ 11 октября 2011

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

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

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

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

6 голосов
/ 11 октября 2011

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

4 голосов
/ 11 октября 2011

Заявление

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

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

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

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

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

1 голос
/ 13 октября 2011

Мне не нравится весь этот стиль кодирования типа «включи» по нескольким причинам. (Приведены примеры, относящиеся к моей отрасли, разработке игр. Заранее извиняюсь. :))

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

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

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

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

1 голос
/ 11 октября 2011

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

0 голосов
/ 12 октября 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...