2009-08-05 3 views
30

В некоторых рекомендациях указано, что вы должны использовать интерфейс, если хотите определить контракт для класса, где наследование нечеткое (IDomesticated) и наследование, когда класс является расширением другого (Cat : Mammal, Snake : Reptile), бывают случаи, когда (на мой взгляд) эти рекомендации входят в серое пространство.Когда использовать интерфейсы или абстрактные классы? Когда использовать оба?

Например, моя реализация была Cat : Pet. Pet - абстрактный класс. Должен ли он быть расширен до Cat : Mammal, IDomesticated, где Mammal является абстрактным классом, а IDomesticated - это интерфейс? Или я вступаю в противоречие с принципами (хотя я не уверен, будет ли в будущем класс Wolf, который не сможет наследовать от Pet)?

Переход от метафорического Cat s и Pet s, допустим, у меня есть классы, которые представляют источники для входящих данных. Все они должны каким-то образом реализовать ту же базу. Я мог бы реализовать некоторый общий код в абстрактном классе Source и наследовать его. Я мог бы также просто создать интерфейс ISource (который чувствует себя более «правильным» для меня) и повторно реализовать общий код в каждом классе (который менее интуитивно понятен). Наконец, я мог «взять пирог и съесть его», сделав как абстрактный класс, так и интерфейс. Что лучше?

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


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

Также стоит отметить, что этот вопрос в основном относится к языкам, которые не поддерживают множественное наследование (например, .NET и Java).

+1

Я хотел бы отметить, что я видел несколько вопросов «Интерфейс против абстрактных классов», но в этом вопросе меня больше всего интересует, когда использовать * оба * интерфейса и абстрактные классы (если это действительная вещь.) – Blixt

+0

Возможный дубликат [Интерфейс против абстрактного класса (общий OO)] (http://stackoverflow.com/questions/761194/interface-vs-abstract-class-general-oo) –

+0

Возможный дубликат [Когда использовать интерфейс вместо абстрактного класса и наоборот?] (Http://stackoverflow.com/questions/479142/when-to-use-an-interface-instead-of-an-abstract-class-and -vice-versa) – nawfal

ответ

33

Как первое эмпирическое правило, я предпочитаю абстрактные классы по интерфейсам, based on the .NET Design Guidelines. Обоснование применяется намного шире, чем .NET, но лучше объясняется в книге Framework Design Guidelines.

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

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

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

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

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

Примером, который приходит на ум, является ASP.NET MVC. Конвейер запросов работает на IController, но есть базовый класс Controller, который обычно используется для реализации поведения.

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


Обновление: Я больше не предпочитают абстрактные классы через интерфейсы, и я не имею в течение длительного времени; вместо этого я предпочитаю композицию над наследованием, используя SOLID в качестве ориентира.

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

+0

Ах спасибо за полезные советы! Я считаю, что интерфейсы считаются «хорошей практикой» (и, следовательно, я пытаюсь найти их для использования), но на практике они действительно не решают много проблем, по крайней мере, не в проектах малого и среднего размера ... Я считаю, что в своем следующем проекте я попытаюсь придерживаться абстрактных базовых классов, если не найду особую вескую причину для перехода с интерфейсом. – Blixt

+2

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

+0

С помощью JDK8 и методов по умолчанию для интерфейсов аргумент версии выглядит еще менее актуальным. Тем не менее интересное различие, о чем можно подумать. – mibollma

2

Я всегда использую эти принципы:

  • Используйте интерфейсы для множественного наследования TYPE (как .NET/Java не использовать множественное наследование)
  • Используйте абстрактные классы для повторного использования реализации типа

Правило доминирующей озабоченности диктует, что у класса всегда есть основная проблема и 0 или более других (см. http://citeseer.ist.psu.edu/tarr99degrees.html). Эти 0 или более других, которые вы затем реализуете через интерфейсы, поскольку класс затем реализует все типы, которые он должен реализовать (собственные и все интерфейсы, которые он реализует).

В мире множественного наследования реализации (например, C++/Eiffel) можно было бы наследовать классы, реализующие интерфейсы. (Теоретически. На практике это может не сработать.)

2

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

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

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

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

interface Foo 
abstract class FooBase implements Foo 
class FunnyFoo extends FooBase 
class SeriousFoo extends FooBase 

Вы также можете иметь более абстрактные классы, наследующие друг от друга для более сложной иерархии.

2

Существует также нечто, называемое принципом DRY - не повторяйте себя.

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

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

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

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

+0

Большинство сценариев, в которых у вас есть интерфейс, не приведет к «нескольким копиям одного и того же кода». – RichardOD

+0

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

19

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

Кошка является млекопитающим, но один из его возможности является то, что это Pettable.

Или, по-другому, классы - существительные, а интерфейсы - ближе к прилагательным.

12

из MSDN, Recommendations for Abstract Classes vs. Interfaces

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

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

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

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

0

см ниже SE вопрос для общих руководящих принципов:

Interface vs Abstract Class (general OO)

Практический случай использования для интерфейса:

  1. Реализация Strategy_pattern: Определите свою стратегию, интерфейс. Динамично переключайте реализацию с помощью одной из конкретных реализаций стратегии во время выполнения.

  2. Определить возможность среди множества несвязанных классов.

Практическое использование дело для абстрактного класса:

  1. Реализация Template_method_pattern: Определить скелет алгоритма. Дочерние классы не могут изменять структуру альгортимов, но они могут переопределить часть реализации в дочерних классах.

  2. Если вы хотите использовать нестатические и не конечные переменные между несколькими связанными классами с «имеет отношение».

Использование обоих abstradt класса и интерфейса:

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

Смежные вопросы