Интерфейс + Расширение (mixin) против базового класса

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

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

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

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

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

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

Ответ 1

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

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

EDIT: Я только подумал о другом преимуществе, которое может иметь значение. Возможно, что некоторые из конкретных реализаций могут предоставить более оптимизированные версии некоторых из общих методов.

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

Ответ 2

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

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

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

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

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

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

Edit:

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

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

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

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

Ответ 3

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

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

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

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

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