Интерфейс vs Базовый класс

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

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

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

Ответ 1

Возьмем ваш пример класса "Собака" и "Кошка", а также проиллюстрируем использование С#:

И собака, и кошка - животные, в частности, четвероногие млекопитающие (животные тоже общие). Предположим, что у вас есть абстрактный класс 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

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

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

Ответ 2

Ну, Джош Блох сказал сам в Эффективный Java 2d:

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

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

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

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

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

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

Интерфейсы

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

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

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

Ответ 6

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

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

Ответ 7

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

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

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

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

Ответ 8

Хуан,

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

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

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

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

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

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

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

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

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

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

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

Ответ 9

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

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

веселит

Ответ 10

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

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

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

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

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

Ответ 11

В этой статье "Яркий мир" хорошо объясняется

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

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

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

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


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

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

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

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

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

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

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

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

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

Ответ 12

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

Ответ 13

У меня грубое правило большого пальца

Функциональность: может быть разной во всех частях: интерфейс.

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

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

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

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

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

Ответ 14

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

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

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

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

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

Ответ 15

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

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

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

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

Ответ 16

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

Ответ 17

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

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

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

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

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

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

Ответ 18

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

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

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

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

Все это хорошо, хорошо и обеспечивает большое повторное использование кода и придерживается принципа DRY (Dont Repeat Yourself) разработки программного обеспечения. Абстрактные классы отлично подходят, если у вас есть отношения "есть" .

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

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 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 implementation of Bark
// Base Class implementation of Bark

// 

Как вы можете видеть, это отличный способ сохранить код DRY и позволить реализовать реализацию базового класса, когда любой из типов может просто полагаться на 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 implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck 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 (человек), ShowHowDuckSwims (утка) и т.д.)

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

Резюме:

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

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

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

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

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

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

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

Ответ 19

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

Ответ 20

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

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

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

Ответ 21

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

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

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

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

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

Ответ 22

Еще один вариант, который следует иметь в виду, это использование отношения "has-a", иначе "реализовано в терминах" или "композиции". Иногда это более чистый, более гибкий способ структурирования, чем использование "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); }
}

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

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

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

Ответ 23

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

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

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

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

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

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

Ответ 24

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

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

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

Ответ 25

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

Ответ 26

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

Ответ 27

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

Ответ 28

Я обнаружил, что шаблон интерфейсa > Аннотация > Конкретный работает в следующем прецеденте:

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)
}

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

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

Ответ 29

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

Ответ 30

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

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