Интерфейс + расширение (mixin) против базового класса - PullRequest
29 голосов
/ 24 апреля 2009

Является ли интерфейс + методы расширения (mixin) предпочтительнее абстрактного класса?

Если ваш ответ "это зависит", от чего это зависит?

Я вижу два возможных преимущества подхода «интерфейс + расширение».

  • Интерфейсы наследуются множественно, а классы - нет.
  • Вы можете использовать методы расширения для непрерывного расширения интерфейсов. (Клиенты, которые реализуют ваш интерфейс, получат вашу новую базовую реализацию, но все равно смогут переопределить ее.)

Я еще не думал о недостатках этого подхода. Там может быть поразительно простой причине, что подход интерфейс + расширение потерпит неудачу.

Две полезные статьи на эту тему:

Ответы [ 3 ]

24 голосов
/ 24 апреля 2009

Недостаток методов расширения: клиенты до C # 3 / VB9 не смогут использовать его так же легко.

Это все, что касается меня - я думаю, что подход на основе интерфейса значительно лучше. Затем вы можете хорошо смоделировать свои зависимости, и все в основном будет менее тесно связано. Я не большой поклонник наследования классов, если это не специализация :))

РЕДАКТИРОВАТЬ: Я только что подумал об одном другом преимуществе, которое может иметь отношение. Возможно, что некоторые из конкретных реализаций могли бы предоставить более оптимизированные версии некоторых из общих методов.

Enumerable.Count является хорошим примером этого - он явно проверяет, реализует ли последовательность IList или нет, потому что, если это так, он может вызвать Count в списке вместо итерации всей последовательности. Если бы IEnumerable<T> был абстрактным классом с виртуальным методом Count(), он мог бы быть переопределен в List<T>, а не в единственной реализации, которая явно знает о IList. Я не говорю, что это всегда актуально, и что IEnumerable<T> должен был быть абстрактным классом (определенно нет!) - просто указываю на это как на небольшой возможный недостаток. Вот где полиморфизм действительно был бы уместен, специализируя существующее поведение (по общему признанию, таким образом, который влияет только на производительность вместо результата).

23 голосов
/ 24 апреля 2009

ИМХО, это неправильный вопрос.

Вы должны использовать все, для чего оно предназначено.

  • Методы расширения не являются членами. Они синтаксически выглядят как члены, так что вам будет проще их найти.
  • Методы расширения могут использовать только открытый (или внутренний) интерфейс. Многие другие классы могут сделать то же самое. Таким образом, методы расширения не являются настоящей инкапсуляцией.
  • Они являются статическими методами и не могут быть переопределены и не могут быть изменены в модульном тесте. Это не языковая функция, и вызывающий абонент статически привязан к ней.

  • Абстрактные базовые классы действительно часто неправильно используют для «повторного использования кода» (вместо реального наследования). Это относится к наследованию в целом.

Вопрос должен звучать так: «Когда мне следует использовать интерфейсы, методы расширения или базовые классы?»

  • используйте интерфейсы всякий раз, когда вам нужен контракт (а это происходит постоянно).
  • Используйте (абстрактные) базовые классы, когда у вас есть ситуация для реального наследования (вы можете написать книгу о том, как это оценить, поэтому я просто оставлю это так). Интерфейс также в основном реализован одновременно.
  • использовать методы расширения для логики, которая на самом деле не должна быть членом типа, потому что ответственность за ее реализацию не лежит на типе - но вы хотите, чтобы ее было легко найти, и это естественно назвать так: член.

Edit:

Или вопрос должен звучать так: «Как написать функциональность многократного использования, не принадлежащую базовому классу?»

  • написать интерфейс, который предоставляет функциональность
  • написать повторно используемый библиотечный класс, который реализует функциональность
  • написать класс, который реализует интерфейс и повторно использует функциональность, объединяя повторно используемый класс.

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

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

1 голос
/ 24 апреля 2009

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

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

Просто придумываю У меня есть интерфейс с именем Math, который имеет сложение, вычитание, деление и умножение, а затем у меня есть класс с именем IntMAth, который реализует Math, который оптимизирован для целочисленной математики, и у меня есть еще один класс с именем FloatMath, который реализует Math, который оптимизирован для Floating Math, и у меня есть GeneralMath, который реализует Math, который обрабатывает все остальное.

Когда мне нужно добавить несколько чисел с плавающей точкой, я мог бы вызвать мою фабрику MathFactory.getMath (typeof (float)), и у нее есть некоторая логика, чтобы знать, что если тип, который я передаю, является float, то он возвращает класс FloatMath.

Таким образом, все мои классы меньше и удобнее в обслуживании, код, который вызывает классы, меньше и т. Д.

...