Вопрос

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

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

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

Это было полезно?

Решение

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Интерфейсы

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

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

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

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

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

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

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

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

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

Хуан,

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

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

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

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

Так что, если вы думаете так же, как я, вы бы наверняка сказали, что Кошка и Собака — IPetable.Эта характеристика соответствует им обоим.

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

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

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

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

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

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

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

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

ваше здоровье

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

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

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

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

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

В этом хорошо объяснили Статья о мире Java

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник: 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).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top