Интерфейс против Базового класса - PullRequest
743 голосов
/ 11 сентября 2008

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

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

Если у меня класс собак и кошек. Почему я хотел бы реализовать IPet вместо PetBase? Я могу понять наличие интерфейсов для ISheds или IBarks (IMakesNoise?), Потому что они могут быть размещены на основе питомца за питомцем, но я не понимаю, какой использовать для обычного питомца.

Ответы [ 39 ]

481 голосов
/ 11 сентября 2008

Давайте возьмем ваш пример классов Dog и Cat и проиллюстрируем это на C #:

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

public abstract class Mammal

Этот базовый класс, вероятно, будет иметь методы по умолчанию, такие как:

  • Поток
  • Mate

Все из которых являются поведением, которое имеет более или менее одинаковую реализацию для обоих видов. Чтобы определить это у вас будет:

public class Dog : Mammal
public class Cat : Mammal

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

public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

Это все равно будет действовать, потому что в основе функциональности Feed() и Mate() все равно будет.

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

public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

Реализация вышеуказанного контракта не будет одинаковой между кошкой и собакой; помещать их реализации в абстрактный класс для наследования будет плохой идеей.

Ваши определения собак и кошек теперь должны выглядеть так:

public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

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

Следовательно, поскольку вы обычно можете наследовать только от одного абстрактного класса (в большинстве статически типизированных ОО-языков, то есть ... исключения включают в себя C ++), но способны реализовывать несколько интерфейсов, это позволяет вам создавать объекты строго * 1034. * по требованию основа.

139 голосов
/ 11 сентября 2008

Ну, сказал себе Джош Блох в Effective Java 2d :

Предпочитают интерфейсы абстрактным классам

Некоторые основные моменты:

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

  • Интерфейсы идеально подходят для определения миксинов . Грубо говоря, mixin - это тип, который класс может реализовать в дополнение к его «первичной тип », чтобы объявить, что он обеспечивает какое-то необязательное поведение. Например, Comparable это смешанный интерфейс, который позволяет классу объявить, что его экземпляры упорядочены относительно другие взаимно сопоставимые объекты.

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

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

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

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

PS .: Купить книгу. Это намного более подробно.

110 голосов
/ 11 сентября 2008

Современный стиль - определение IPet и PetBase.

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

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

103 голосов
/ 15 сентября 2008

Интерфейсы и базовые классы представляют две разные формы отношений.

Наследование (базовые классы) представляют собой отношения "есть". Например. собака или кошка - это домашнее животное. Это отношение всегда представляет (одиночную) цель класса (в сочетании с «принципом единой ответственности» ).

Интерфейсы , с другой стороны, представляют дополнительные функции класса. Я бы назвал это отношением «есть», как в «Foo одноразовый», следовательно, интерфейс IDisposable в C #.

61 голосов
/ 11 сентября 2008

Интерфейсы

  • Определяет контракт между 2 модулями. Не может иметь никакой реализации.
  • Большинство языков позволяют реализовать несколько интерфейсов
  • Изменение интерфейса является серьезным изменением. Все реализации должны быть перекомпилированы / изменены.
  • Все участники являются публичными. Реализации должны реализовывать все члены.
  • Интерфейсы помогают в развязке. Вы можете использовать макет фреймворков, чтобы макетировать что-нибудь за интерфейсом
  • Интерфейсы обычно указывают на некое поведение
  • Реализации интерфейса отделены / изолированы друг от друга

Базовые классы

  • Позволяет вам добавить стандартную реализацию, которую вы получаете бесплатно путем деривации
  • За исключением C ++, вы можете наследовать только один класс. Даже если это может быть из нескольких классов, это обычно плохая идея.
  • Смена базового класса относительно проста. Производные не нужно делать ничего особенного
  • Базовые классы могут объявлять защищенные и общедоступные функции, к которым могут обращаться производные
  • Абстрактные Базовые классы не могут быть легко смоделированы как интерфейсы
  • Базовые классы обычно указывают иерархию типов (IS A)
  • Деривации классов могут зависеть от некоторого базового поведения (иметь сложные знания родительской реализации). Вещи могут быть грязными, если вы внесете изменения в базовую реализацию для одного парня и сломаете других.
57 голосов
/ 11 сентября 2008

В целом, вы должны отдавать предпочтение интерфейсам перед абстрактными классами. Одна из причин использовать абстрактный класс - если у вас есть общая реализация среди конкретных классов. Конечно, вы все равно должны объявить интерфейс (IPet) и иметь абстрактный класс (PetBase), реализующий этот интерфейс. Используя небольшие отдельные интерфейсы, вы можете использовать множественные числа для дальнейшего повышения гибкости. Интерфейсы обеспечивают максимальную гибкость и переносимость типов через границы. При передаче ссылок через границы всегда передавайте интерфейс, а не конкретный тип. Это позволяет принимающей стороне определить конкретную реализацию и обеспечивает максимальную гибкость. Это абсолютно верно при программировании в режиме TDD / BDD.

«Брига четырех» заявила в своей книге «Поскольку наследование подвергает подкласс деталям реализации его родителя, часто говорят, что« наследование нарушает инкапсуляцию ». Я верю, что это правда.

48 голосов
/ 11 сентября 2008

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

Кшиштоф Квалина говорит на странице 81:

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

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

19 голосов
/ 12 сентября 2008

Juan

Мне нравится думать об интерфейсах как о способе описания класса. Конкретный класс породы собак, например, YorkshireTerrier, может быть потомком родительского класса собак, но он также реализует IFurry, IStubby и IYippieDog. Таким образом, класс определяет, что это за класс, но интерфейс говорит нам об этом.

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

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

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

Так что, если вы думаете, как я, вы определенно скажете, что Cat и Dog являются IPettable. Это характеристика, которая соответствует им обоим.

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

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

Или они должны быть млекопитающими? Может быть, нам нужен какой-нибудь завод по производству животных?

Они вообще должны быть связаны друг с другом? Достаточно ли просто знать, что они оба IPettable?

Я часто испытываю желание вывести целую иерархию классов, когда мне действительно нужен только один класс. Я делаю это в ожидании, когда-нибудь мне это понадобится, и обычно я никогда этого не делаю. Даже когда я это делаю, мне обычно приходится много делать, чтобы это исправить. Это потому, что первый класс, который я создаю, это не Собака, мне не так повезло, это вместо Утконоса. Теперь вся моя иерархия классов основана на странном случае, и у меня много потерянного кода.

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

16 голосов
/ 15 октября 2008

Вот базовое и простое определение интерфейса и базового класса:

  • Базовый класс = наследование объекта.
  • Интерфейс = функциональное наследование.

ура

12 голосов
/ 11 сентября 2008

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

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

Таким образом, вместо IPet или PetBase, вы можете получить Pet с параметром IFurBehavior. Параметр IFurBehavior устанавливается методом CreateDog () PetFactory. Именно этот параметр вызывается для метода shed ().

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

Я рекомендую этот шаблон даже в языках множественного наследования.

...