Вопрос:

Интерфейс против Базового класса

oop interface language-agnostic base-class static-typing

147787 просмотра

30 ответа

1722 Репутация автора

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

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

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

Автор: Howler Источник Размещён: 11.09.2008 03:20

Ответы (30)


110 плюса

60380 Репутация автора

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

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

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

Автор: Jason Cohen Размещён: 11.09.2008 03:22

57 плюса

21349 Репутация автора

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

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

Автор: Kilhoffer Размещён: 11.09.2008 03:22

5 плюса

318251 Репутация автора

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

Автор: Joel Coehoorn Размещён: 11.09.2008 03:24

2 плюса

18322 Репутация автора

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

Недостатком реализации базового класса является требование override(или new) существующих методов. Это делает их виртуальными методами, что означает, что вы должны быть осторожны с тем, как вы используете экземпляр объекта.

Наконец, единственное наследство .NET убивает меня. Наивный пример: скажем, вы делаете пользовательский элемент управления, поэтому вы наследуете UserControl. Но теперь вы заблокированы от наследования PetBase. Это вынуждает вас реорганизоваться, например, чтобы сделать PetBaseчлена класса вместо этого.

Автор: spoulson Размещён: 11.09.2008 03:29

2 плюса

2395 Репутация автора

@Joel: некоторые языки (например, C ++) допускают множественное наследование.

Автор: Einar Размещён: 11.09.2008 03:30

2 плюса

302675 Репутация автора

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

Автор: Bill the Lizard Размещён: 11.09.2008 03:31

139 плюса

11913 Репутация автора

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

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

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

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

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

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

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

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

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

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

Автор: Marcio Aguiar Размещён: 11.09.2008 03:32

7 плюса

17390 Репутация автора

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

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

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

Абстрактные классы могут содержать несколько интерфейсов. Ваш абстрактный класс PetBase может реализовывать IPet (домашние животные имеют владельцев) и IDigestion (домашние животные едят или, по крайней мере, должны). Тем не менее, PetBase, вероятно, не будет реализовывать IMammal, поскольку не все домашние животные являются млекопитающими и не все млекопитающие являются домашними животными. Вы можете добавить MammalPetBase, который расширяет PetBase, и добавить IMammal. FishBase может иметь PetBase и добавить IFish. IFish будет иметь ISwim и IUnderwaterBreather в качестве интерфейсов.

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

Автор: Jarrett Meyer Размещён: 11.09.2008 03:34

484 плюса

81615 Репутация автора

Решение

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

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

public abstract class Mammal

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

  • Кормить
  • Приятель

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

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 ++), но иметь возможность реализовывать несколько интерфейсов, это позволяет вам создавать объекты строго по мере необходимости .

Автор: Jon Limjap Размещён: 11.09.2008 03:36

62 плюса

105149 Репутация автора

Интерфейсы

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

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

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

48 плюса

3717 Репутация автора

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

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

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

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

Автор: fryguybob Размещён: 11.09.2008 04:18

1 плюс

12837 Репутация автора

Наследник базового класса должен иметь отношение «есть». Интерфейс представляет отношение «реализует». Поэтому используйте базовый класс только тогда, когда ваши наследники будут поддерживать отношения.

Автор: Aaron Fischer Размещён: 11.09.2008 06:23

12 плюса

0 Репутация автора

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

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

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

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

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

Автор: Joe Размещён: 11.09.2008 07:03

19 плюса

2411 Репутация автора

Хуан,

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

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

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

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

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

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

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

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

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

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

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

Автор: Flory Размещён: 11.09.2008 09:32

3 плюса

3989 Репутация автора

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

Автор: Scott Lawrence Размещён: 11.09.2008 10:01

3 плюса

5663 Репутация автора

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

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

Короче говоря, если A «представляет собой» B, то A требует не более B и обеспечивает не менее B для каждого раскрываемого им метода.

Автор: theschmitzer Размещён: 12.09.2008 09:24

10 плюса

4437 Репутация автора

Также имейте в виду, что в OO не смейтесь ( см. Блог ) и всегда моделируйте объекты, основываясь на требуемом поведении. Если вы разрабатываете приложение, в котором вам требуется только общее название и вид животного, вам нужно только один класс Animal со свойством для имени, вместо миллионов классов для каждого возможного животного в мире.

Автор: Sijin Размещён: 15.09.2008 06:54

108 плюса

3697 Репутация автора

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

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

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

Автор: Thomas Danecker Размещён: 15.09.2008 07:25

3 плюса

672 Репутация автора

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

Классы предоставляют реализацию, они могут объявить, что они реализуют ноль, один или несколько интерфейсов. Если класс предназначен для наследования, соглашение заключается в добавлении префикса имени класса к «Base».

Существует различие между базовым классом и абстрактным базовым классом (ABC). ABC смешивают интерфейс и реализацию вместе. Аннотация за пределами компьютерного программирования означает «резюме», то есть «Аннотация == Интерфейс». Затем абстрактный базовый класс может описывать как интерфейс, так и пустую, частичную или полную реализацию, которая предназначена для наследования.

Мнения о том, когда использовать интерфейсы или абстрактные базовые классы, а не только классы, будут сильно различаться в зависимости от того, что вы разрабатываете, и от того, на каком языке вы разрабатываете. Интерфейсы часто ассоциируются только со статически типизированными языками, такими как Java или C #, но Динамически типизированные языки также могут иметь интерфейсы и абстрактные базовые классы. Например, в Python различие проводится между классом, который объявляет, что он реализует интерфейс, и объектом, который является экземпляром класса, и, как говорят, обеспечивает этот интерфейс. В динамическом языке возможно, что два объекта, которые являются экземплярами одного и того же класса, могут объявить, что они предоставляют совершенно разныеинтерфейсы. В Python это возможно только для атрибутов объекта, в то время как методы являются общим состоянием для всех объектов класса. Однако в Ruby объекты могут иметь методы для каждого экземпляра, поэтому вполне возможно, что интерфейс между двумя объектами одного и того же класса может изменяться настолько, насколько этого желает программист (однако в Ruby нет никакого явного способа объявления интерфейсов).

В динамических языках Интерфейс к объекту часто неявно предполагается, либо путем самоанализа объекта и спрашивая его, какие методы он предоставляет (Look Before You Leap), либо, предпочтительно, просто пытаясь использовать требуемый интерфейс на объекте и перехватывая исключения, если объект не предоставляет этот интерфейс (проще просить прощения, чем разрешения). Это может привести к «ложным срабатываниям», когда два интерфейса имеют одинаковое имя метода, но семантически различаются, однако компромисс заключается в том, что ваш код более гибок, так как вам не нужно заранее указывать заранее, чтобы предвидеть все возможные применения вашего кода.

Автор: Wheat Размещён: 15.09.2008 10:34

16 плюса

999 Репутация автора

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

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

ура

Автор: RWendi Размещён: 15.10.2008 06:19

12 плюса

17898 Репутация автора

Хорошо объяснено в этой статье Java World

Лично я склонен использовать интерфейсы для определения интерфейсов - то есть частей системного дизайна, которые определяют, как к чему-то получить доступ.

Нередко у меня будет класс, реализующий 1 или более интерфейсов.

Абстрактные классы я использую как основу для чего-то другого.

Ниже приведен отрывок из вышеупомянутой статьи JavaWorld.com, автор Тони Синтес, 20.04.01


Интерфейс против абстрактного класса

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

Абстрактные классы позволяют вам определять некоторые поведения; они заставляют ваши подклассы предоставлять другим. Например, если у вас есть платформа приложения, абстрактный класс может предоставлять службы по умолчанию, такие как обработка событий и сообщений. Эти сервисы позволяют вашему приложению подключаться к вашей структуре приложения. Тем не менее, есть некоторые специфичные для приложения функции, которые может выполнять только ваше приложение. Такая функциональность может включать задачи запуска и завершения работы, которые часто зависят от приложения. Поэтому вместо того, чтобы пытаться определить это поведение, абстрактный базовый класс может объявлять абстрактные методы завершения работы и запуска. Базовый класс знает, что ему нужны эти методы, но абстрактный класс позволяет вашему классу признать, что он не знает, как выполнять эти действия; он только знает, что должен инициировать действия. Когда пора начинать, абстрактный класс может вызывать метод запуска. Когда базовый класс вызывает этот метод, Java вызывает метод, определенный дочерним классом.

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

Класс против интерфейса

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

Например, шаблон «Стратегия» позволяет вам добавлять новые алгоритмы и процессы в вашу программу, не изменяя объекты, которые их используют. Медиаплеер может знать, как воспроизводить CD, MP3 и WAV файлы. Конечно, вы не хотите жестко закодировать эти алгоритмы воспроизведения в проигрыватель; это затруднит добавление нового формата, такого как AVI. Кроме того, ваш код будет завален бесполезными инструкциями. И чтобы добавить оскорбление к травме, вам нужно будет обновлять эти утверждения случая каждый раз, когда вы добавляете новый алгоритм. В общем, это не очень объектно-ориентированный способ программирования.

С помощью шаблона «Стратегия» вы можете просто инкапсулировать алгоритм за объектом. Если вы это сделаете, вы можете предоставить новые медиа-плагины в любое время. Давайте назовем плагин класса MediaStrategy. Этот объект будет иметь один метод: playStream (Stream s). Таким образом, чтобы добавить новый алгоритм, мы просто расширяем наш класс алгоритма. Теперь, когда программа встречает новый тип медиа, она просто делегирует воспроизведение потока нашей медиа стратегии. Конечно, вам понадобится немного сантехники, чтобы правильно реализовать алгоритмы стратегий, которые вам понадобятся.

Это отличное место для использования интерфейса. Мы использовали шаблон Стратегии, который четко указывает место в дизайне, которое изменится. Таким образом, вы должны определить стратегию как интерфейс. Обычно вы должны отдавать предпочтение интерфейсам, а не наследованию, когда хотите, чтобы объект имел определенный тип; в этом случае MediaStrategy. Опора на наследование для идентификации типа опасна; это блокирует вас в определенной иерархии наследования. Java не допускает множественного наследования, поэтому вы не можете расширять что-то, что дает вам полезную реализацию или большую идентичность типов.

Автор: Richard Harrison Размещён: 16.10.2008 08:39

3 плюса

6034 Репутация автора

Другой вариант, о котором следует помнить, - это использование отношения «имеет», иначе «реализуется в терминах» или «состав». Иногда это более чистый и более гибкий способ структурирования вещей, чем использование наследования "is-a".

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

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

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

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

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

Автор: Parappa Размещён: 03.11.2008 10:30

6 плюса

3151 Репутация автора

Случай с базовыми классами через интерфейсы был хорошо объяснен в Руководстве по кодированию Submain .NET:

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

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

  1. Несколько несвязанных классов хотят поддерживать протокол.
  2. Эти классы уже имеют установленные базовые классы (например, некоторые являются элементами управления пользовательского интерфейса (UI), а некоторые - веб-службами XML).
  3. Агрегирование не подходит или практически невозможно. Во всех других ситуациях наследование классов является лучшей моделью.
Автор: YeahStu Размещён: 18.11.2008 08:25

4 плюса

0 Репутация автора

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

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

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

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

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

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

Автор: Don Edwards Размещён: 11.12.2008 10:14

2 плюса

2888 Репутация автора

Что касается C #, то в некотором смысле интерфейсы и абстрактные классы могут быть взаимозаменяемыми. Однако различия заключаются в следующем: i) интерфейсы не могут реализовывать код; ii) из-за этого интерфейсы не могут далее вызывать стек до подкласса; и iii) только абстрактный класс может быть унаследован от класса, тогда как в классе может быть реализовано несколько интерфейсов.

Автор: CarneyCode Размещён: 23.02.2011 11:51

9 плюса

10519 Репутация автора

У меня есть грубое эмпирическое правило

Функциональность: вероятно, будет отличаться во всех частях: Интерфейс.

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

Данные и функциональность, реально работающие, если расширены только с небольшими изменениями: обычный (конкретный) класс

Данные и функциональность, никаких изменений не планируется: обычный (конкретный) класс с финальным модификатором.

Данные и, возможно, функциональность: только для чтения: члены enum.

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

Автор: Akhil Jain Размещён: 14.10.2012 08:56

2 плюса

72 Репутация автора

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

Автор: Ganesh Kodiganti Размещён: 23.09.2014 01:54

6 плюса

3691 Репутация автора

Источник : http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/

C # - замечательный язык, который вырос и развился за последние 14 лет. Это здорово для нас, разработчиков, потому что зрелый язык предоставляет нам множество языковых возможностей, которые находятся в нашем распоряжении.

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

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

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

Например: Золотистый ретривер - это тип собаки. Так же как и пудель. Они оба могут лаять, как все собаки. Тем не менее, вы можете указать, что парк пуделей значительно отличается от лая для собак по умолчанию. Поэтому для вас может иметь смысл реализовать следующее:

public abstract class Dog
{
      public virtual void Bark()
      {
        Console.WriteLine("Base Class implementation of Bark");
      }
}

public class GoldenRetriever : Dog
{
   // the Bark method is inherited from the Dog class
}

public class Poodle : Dog
{
  // here we are overriding the base functionality of Bark with our new implementation
  // specific to the Poodle class
  public override void Bark()
  {
     Console.WriteLine("Poodle's implementation of Bark");
  }
}

// Add a list of dogs to a collection and call the bark method.

void Main()
{
    var poodle = new Poodle();
    var goldenRetriever = new GoldenRetriever();

    var dogs = new List<Dog>();
    dogs.Add(poodle);
    dogs.Add(goldenRetriever);

    foreach (var dog in dogs)
    {
       dog.Bark();
    }
}

// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark

// 

Как вы можете видеть, это было бы отличным способом сохранить ваш код СУХИМЫМ и позволить вызывать реализацию базового класса, когда любой из типов может просто полагаться на стандартную Bark вместо специальной реализации. Такие классы, как GoldenRetriever, Boxer, Lab, все могут унаследовать Bark «по умолчанию» (класс баса) просто потому, что они реализуют абстрактный класс Dog.

Но я уверен, что вы уже знали это.

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

Что, черт возьми, это значит? Ну, например: человек не утка ... и утка не человек. Достаточно очевидно. Однако и утка, и человек обладают способностью плавать (учитывая, что человек прошел свои уроки плавания в 1-м классе :)). Кроме того, поскольку утка - это не человек или наоборот, это не «реальность», а отношения «способен», и мы можем использовать интерфейс, чтобы проиллюстрировать это:

// Create ISwimable interface
public interface ISwimable
{
      public void Swim();
}

// Have Human implement ISwimable Interface
public class Human : ISwimable

     public void Swim()
     {
        //Human's implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck's implementation of Swim
          Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
     }
}

//Now they can both be used in places where you just need an object that has the ability "to swim"

public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
     somethingThatCanSwim.Swim();
}

public void Main()
{
      var human = new Human();
      var duck = new Duck();

      var listOfThingsThatCanSwim = new List<ISwimable>();

      listOfThingsThatCanSwim.Add(duck);
      listOfThingsThatCanSwim.Add(human);

      foreach (var something in listOfThingsThatCanSwim)
      {
           ShowHowYouSwim(something);
      }
}

 // So at runtime the correct implementation of something.Swim() will be called
 // Output:
 // Quack! Quack! I'm a Duck swimming!
 // I'm a human swimming!

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

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

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

Резюме:

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

С другой стороны, используйте интерфейс, когда у вас нет отношений «есть», но есть типы, которые разделяют «способность» что-то делать или что-то иметь (например, утка «не» является человеком. Однако утка и человеческая доля «Умение» плавать).

Еще одно отличие, которое следует отметить между абстрактными классами и интерфейсами, заключается в том, что класс может реализовывать один-много интерфейсов, но класс может наследовать только от ОДНОГО абстрактного класса (или любого класса в этом отношении). Да, вы можете вкладывать классы и иметь иерархию наследования (что многие программы делают и должны иметь), но вы не можете наследовать два класса в одном определении производного класса (это правило применяется к C #. В некоторых других языках вы можете сделать это, обычно только из-за отсутствия интерфейсов на этих языках).

Также помните, что при использовании интерфейсов следует придерживаться принципа разделения интерфейса (ISP). Провайдер заявляет, что ни один клиент не должен зависеть от методов, которые он не использует. По этой причине интерфейсы должны быть ориентированы на конкретные задачи и обычно очень малы (например, IDisposable, IComparable).

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

Надеюсь, это прояснит ситуацию для некоторых людей!

Также, если вы можете придумать какие-нибудь лучшие примеры или хотите что-то указать, пожалуйста, сделайте это в комментариях ниже!

Автор: Jason Roell Размещён: 09.12.2014 08:42

4 плюса

3375 Репутация автора

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

Обоснование, основные моменты для рассмотрения [два уже упомянутых здесь] являются:

  • Интерфейсы более гибкие, потому что класс может реализовывать несколько интерфейсов. Поскольку Java не имеет множественного наследования, использование абстрактных классов не позволяет вашим пользователям использовать любую другую иерархию классов. В общем, предпочитайте интерфейсы, когда нет реализаций по умолчанию или состояния. Коллекции Java предлагают хорошие примеры этого (Map, Set и т. Д.).
  • У абстрактных классов есть преимущество, заключающееся в том, что они обеспечивают лучшую совместимость. Когда клиенты используют интерфейс, вы не можете его изменить; если они используют абстрактный класс, вы все равно можете добавить поведение, не нарушая существующий код. Если проблема совместимости, подумайте об использовании абстрактных классов.
  • Даже если у вас есть реализации по умолчанию или внутреннее состояние, рассмотрите возможность предложить интерфейс и его абстрактную реализацию . Это поможет клиентам, но все же предоставит им большую свободу при желании [1].
    Конечно, предмет был обсужден подробно в другом месте [2,3].

[1] Конечно, он добавляет больше кода, но если краткость является вашей главной задачей, вам, вероятно, следовало бы избегать Java в первую очередь!

[2] Джошуа Блох, Эффективная Ява, пункты 16-18.

[3] http://www.codeproject.com/KB/ar ...

Автор: Jaimin Patel Размещён: 27.02.2016 11:43

2 плюса

5311 Репутация автора

Я обнаружил, что шаблон Interface> Abstract> Concrete работает в следующем сценарии использования:

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

Абстрактный класс определяет общие атрибуты по умолчанию для конкретных классов, но обеспечивает интерфейс. Например:

public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

Теперь, поскольку у всех млекопитающих есть волосы и соски (AFAIK, я не зоолог), мы можем свернуть это в абстрактный базовый класс

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

И тогда конкретные классы просто определяют, что они идут прямо.

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

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

В этом случае реферат также может быть конкретным; однако, абстрактное обозначение помогает подчеркнуть, что этот образец используется.

Автор: Adam Hughes Размещён: 04.03.2016 06:23
Вопросы из категории :
32x32