Подтвердить что ты не робот

Когда следует выбирать наследование над интерфейсом при разработке библиотек классов С#?

У меня есть класс Processor, который будет делать две очень разные вещи, но вызывается из общего кода (ситуация "инверсия управления" ).

Мне интересно, какие соображения дизайна я должен знать (или осознавать, для вас USsers), когда решаете, должны ли они все наследовать от BaseProcessor, или реализовать IProcessor в качестве интерфейса.

4b9b3361

Ответ 1

Как правило, правило выглядит примерно так:

  • Наследование описывает отношение is-a.
  • Реализация интерфейса описывает отношения can-do.

Чтобы сделать это в несколько более конкретных терминах, рассмотрим пример. System.Drawing.Bitmap класс is-an образ (и как таковой он наследует от класса Image), но также и can-do, поэтому он реализует интерфейс IDisposable. Это также сериализация can-do, поэтому она реализуется из интерфейса ISerializable.

Но более практично, интерфейсы часто используются для имитации множественного наследования в С#. Если ваш класс Processor должен наследовать от чего-то вроде System.ComponentModel.Component, тогда у вас мало выбора, кроме как реализовать интерфейс IProcessor.

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

Для ответов на такие вопросы я часто обращаюсь к .NET Framework Design Guidelines, в котором говорится об выборе между классами и интерфейсы:

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

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

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

[., ]

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

Их общие рекомендации заключаются в следующем:

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

Крис Андерсон выражает особое согласие с этим последним принципом, утверждая, что:

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

Ответ 2

Ричард,

Почему ВЫ ВЫБИРАЕТЕ между ними? У меня был бы интерфейс IProcessor как опубликованный тип (для использования в другом месте в системе); и если так получится, что ваши различные CURRENT-реализации IProcessor имеют общее поведение, тогда абстрактный класс BaseProcessor станет хорошим местом для реализации этого общего поведения.

Таким образом, если в будущем вам понадобится IProcessor, который НЕ был для служб BaseProcessor, он НЕ ИМЕЕТ его (и, возможно, скрыть)... но те, кто этого хочет, могут иметь это. Сокращение в дублированном коде/понятиях.

Просто мое скромное МНЕНИЕ.

Приветствия. Кит.

Ответ 3

Интерфейсы - это "контракт", это гарантирует, что некоторый класс реализует желаемый набор элементов - свойства, методы и события -.

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

Когда использовать интерфейсы?

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

Когда использовать базовые классы (конкретные и/или абстрактные)

Всякий раз, когда группа объектов имеет один и тот же архетип, значение B наследует A, потому что B есть A с различиями, но B можно идентифицировать как A.


Примеры:

Расскажите о таблицах.

  • Мы принимаем перерабатываемые таблицы = > Это должно быть определено с помощью интерфейса типа IRecyclableTable, гарантирующего, что все перерабатываемые таблицы будут иметь метод "Recycle".

    /li >
  • Нам нужны настольные таблицы = > Это должно быть определено с наследованием. "Настольная таблица" - это "таблица". Все таблицы имеют общие свойства и поведение, а на рабочих столах одни и те же, добавляя такие вещи, которые делают рабочую таблицу другой, чем другие типы таблиц.

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

Ответ 4

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

Ответ 5

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

Или, если наследование нельзя избежать, возможно, хорошая идея использовать оба из них вместе. Создайте абстрактный базовый класс, который реализует интерфейс. В вашем случае ProcessorBase реализует IProcessor.

Аналогично ControllerBase и IController в ASP.NET Mvc.

Ответ 6

Отбросить чистоту в сторону, вы можете наследовать только один раз, поэтому, если вам нужно наследовать какой-то класс фреймворка, вопрос будет спорным.

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

EDIT:

Если базовый класс будет иметь некоторую реализацию, тогда это может быть полезно, если оно чисто абстракто, тогда это может быть и интерфейс.