Интерфейс vs базовый класс
когда я должен использовать интерфейс, и когда я должен использовать базовый класс?
должен ли он всегда быть интерфейсом, если я не хочу фактически определять базовую реализацию методов?
Если у меня есть класс собаки и кошки. Почему я хочу реализовать IPet вместо PetBase? Я могу понять наличие интерфейсов для ISheds или IBarks (IMakesNoise?), потому что они могут быть размещены на домашних животных на основе домашних животных, но я не понимаю, что использовать для общего питомца.
30 ответов
Давайте возьмем Ваш пример класса собаки и кошки и проиллюстрируем с помощью C#:
и собака, и кошка-Животные, в частности, четвероногие млекопитающие(животные слишком общие). Предположим, что у вас есть абстрактный класс Mammal, для обоих из них:
public abstract class Mammal
этот базовый класс, вероятно, будет иметь методы по умолчанию, такие как:
- Feed
- мат
все из которых являются поведение, которое более или менее одинаковая реализация между обоими видами. Чтобы определить это, вам придется:
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... исключения включают C++), но иметь возможность реализовать несколько интерфейсов, это позволяет создавать объекты в строго как основы.
Ну, Джош блох сказал сам в эффективная Java 2d:
предпочитают интерфейсы абстрактным классам
основные идеи:
существующие классы могут быть легко модифицированы для реализации нового интерфейс. Все, что вам нужно сделать, это добавить необходимые методы, если они еще не exist и добавьте предложение implements в объявление класса.
интерфейсы идеально подходит для определения mixins. Грубо говоря, mixin-это тип, который класс может реализовать в дополнение к своей "основной введите", чтобы объявить, что он предоставляет некоторое необязательное поведение. Например, Сопоставимый интерфейс mixin, который позволяет классу объявить, что его экземпляры упорядочены в отношении других сравнимых объектов.
интерфейсы позволяют конструкции неиерархического типа рамки. Тип иерархии отлично подходит для организации некоторых вещей, но другие вещи не попадают аккуратно в жесткая иерархия.
интерфейсы включают безопасные, мощные улучшения функциональности через идиома wrap - per class. Если вы используете абстрактные классы для определения типов, вы оставьте программиста, который хочет добавить функциональность без альтернативы использовать наследование.
кроме того, вы можете объединить достоинства из интерфейсы и абстрактные классы предоставление абстрактного скелета класс реализации для каждого нетривиальный интерфейс, который вы экспортируете.
с другой стороны, интерфейсы очень трудно развиваться. Если вы добавите метод в интерфейс, он сломает все его реализации.
PS. Купи книгу. Это гораздо более подробно.
современный стиль, чтобы определить IPet и PetBase.
преимущество интерфейса заключается в том, что другой код может использовать его без каких-либо связей с другим исполняемым кодом. Полностью очистить."Также интерфейсы могут быть смешанными.
но базовые классы полезны для простых реализаций и общих утилит. Поэтому предоставьте абстрактный базовый класс, чтобы сэкономить время и код.
интерфейсы и базовые классы представляют две различные формы отношений.
наследование (базовые классы) представляют собой отношение "is-a". Е. Г. собака или кошка "-" питомца. Это отношение всегда представляет собой (single)цель класса (в сочетании с "принцип единой ответственности").
интерфейсы, С другой стороны, представляют собой дополнительные возможности класса. Я бы назвал это отношениями "есть", как в "Foo
одноразовая", поэтому IDisposable
интерфейс в C#.
интерфейсы
- определяет контракт между 2 модулями. Не может иметь никакой реализации.
- большинство языков позволяют реализовать несколько интерфейсов
- изменение интерфейса является критическим изменением. Все реализации должны быть перекомпилированы / изменены.
- все члены являются открытыми. Реализации должны реализовывать все члены.
- интерфейсы помогают в развязке. Вы можете использовать mock Framework для макета чего угодно за интерфейсом
- интерфейсы обычно указывают на своего рода поведение
- реализации интерфейса развязаны / изолированы друг от друга
базовые классы
- позволяет добавить по умолчанию реализация, которую вы получаете бесплатно по деривации
- кроме C++, вы можете наследовать только от одного класса. Даже если это возможно из нескольких классов, это обычно плохая идея.
- изменение базы класс относительно прост. Деривации не нужно делать ничего особенного
- базовые классы могут объявлять защищенные и общедоступные функции, к которым можно получить доступ с помощью производных
- абстрактные базовые классы не могут быть легко высмеяны, как интерфейсы
- базовые классы обычно указывают иерархию типов (IS A)
- деривации классов могут зависеть от некоторого базового поведения (имеют сложные знания родительской реализации). Все может быть запутано, если ты изменишь базовая реализация для одного парня и сломать другие.
В общем, вы должны отдавать предпочтение интерфейсам над абстрактными классами. Одной из причин использования абстрактного класса является наличие общей реализации среди конкретных классов. Конечно, вы все равно должны объявить интерфейс (IPet) и иметь абстрактный класс (PetBase), реализующий этот интерфейс.Используя небольшие, отдельные интерфейсы, вы можете использовать кратные для дальнейшего повышения гибкости. Интерфейсы обеспечивают максимальную гибкость и переносимость типов через границы. При передаче ссылок через границы, всегда проходят интерфейс, а не конкретный тип. Это позволяет принимающей стороне определить конкретную реализацию и обеспечивает максимальную гибкость. Это абсолютно верно при программировании в режиме TDD / BDD.
банда из четырех человек заявила в своей книге "поскольку наследование предоставляет подкласс деталям реализации своего родителя, часто говорят, что"наследование нарушает инкапсуляцию". Я верю, что это правда.
Это довольно специфично для .NET, но руководство по дизайну фреймворка утверждает, что в общих классах дают больше гибкости в развивающейся структуре. Как только интерфейс будет отправлен, вы не получите возможность изменить его, не нарушая код, который использовал этот интерфейс. С классом, однако, вы можете изменить его и не нарушать код, который ссылается на него. Пока вы делаете правильные изменения, которые включают в себя добавление новых функций, вы сможете расширить и развить свой код.
Кшиштоф Цвалина говорит на странице 81:
в течение трех версий .NET Framework я говорил об этом руководстве с несколькими разработчиками в нашей команде. Многие из них, в том числе те, кто изначально не согласился с рекомендациями, заявили, что сожалеют о том, что отправили некоторый API в качестве интерфейса. Я не слышал ни об одном случае, когда кто-то сожалел, что они отправили класс.
Это было сказано конечно, есть место для интерфейсов. В качестве общего руководства всегда предоставляйте абстрактную реализацию базового класса интерфейса, если ни для чего другого, как пример способа реализации интерфейса. В лучшем случае этот базовый класс сэкономит много работы.
Хуан
Мне нравится думать об интерфейсах как о способе характеристики класса. Конкретный класс породы собак, скажем йоркширский терьер, может быть потомком родительского класса собак, но он также реализует IFurry, IStubby и IYippieDog. Таким образом, класс определяет, что такое класс, но интерфейс говорит нам о нем.
преимущество этого в том, что это позволяет мне, например, собрать все Iyippiedog'S и бросить их в мою коллекцию океана. Так что теперь я могу охватите определенный набор объектов и найдите те, которые соответствуют критериям, на которые я смотрю, не проверяя класс слишком близко.
Я считаю, что интерфейсы действительно должны определять подмножество публичного поведения класса. Если он определяет все публичное поведение для всех реализующих классов, то обычно он не должен существовать. Они не говорят мне ничего полезного.
эта мысль идет вразрез с идеей, что каждый класс должен иметь интерфейс и вы должны закодировать интерфейс. Это нормально, но в конечном итоге у вас много интерфейсов один к одному для классов, и это делает вещи запутанными. Я понимаю, что идея в том, что это действительно ничего не стоит, и теперь вы можете легко менять вещи. Однако я редко это делаю. Большую часть времени я просто изменяю существующий класс на месте и имею те же самые проблемы, которые я всегда делал, если открытый интерфейс этого класса нуждается в изменении, за исключением того, что теперь мне нужно изменить это в двух местах.поэтому, если вы думаете, как я, вы определенно скажете, что кошка и собака IPettable. Это характеристика, которая соответствует им обоим.
другая часть этого, хотя должны ли они иметь тот же базовый класс? Вопрос в том, нужно ли их широко рассматривать как одно и то же. Конечно, они оба животные, но это соответствует тому, как мы будем использовать их вместе.
скажем, я хочу собрать все классы животных и поместить их в свой ковчег контейнер.
или они должны быть млекопитающими? Может, нам нужна какая-нибудь доильная фабрика?
Они вообще должны быть связаны друг с другом? Достаточно ли просто знать, что они оба IPettable?
Я часто чувствую желание получить целую иерархию классов, когда мне действительно нужен только один класс. Я делаю это в предвкушении, что когда-нибудь мне это понадобится, и обычно я никогда этого не делаю. Даже когда я это делаю, я обычно нахожу, что мне нужно многое сделать, чтобы исправить это. Это потому, что первый класс, который я создаю, - это не собака, мне не так повезло, это Утконос. Теперь вся моя иерархия классов основана на странном случае, и у меня много потерянного кода.
в какой-то момент Вы также можете обнаружить, что не все кошки IPettable (например, безволосый). Теперь вы можете переместить этот интерфейс во все производные классы, которые подходят. Вы обнаружите, что гораздо менее разрушительное изменение, которое внезапно кошки больше не являются производными от PettableBase.
вот основное и простое определение интерфейса и базового класса:
- базовый класс = объект наследования.
- Interface = функциональное наследование.
ура
Я рекомендую использовать композицию вместо наследования, когда это возможно. Используйте интерфейсы, но используйте объекты-члены для базовой реализации. Таким образом, вы можете определить фабрику, которая создает ваши объекты для определенного поведения. Если вы хотите изменить поведение, вы создаете новый заводской метод (или абстрактную фабрику), который создает различные типы подобъектов.
в некоторых случаях, вы можете обнаружить, что ваши первичные объекты не нужны интерфейсы, если все изменяемое поведение определяется в вспомогательных объектах.
поэтому вместо IPet или PetBase вы можете получить домашнее животное, которое имеет параметр IFurBehavior. Параметр IFurBehavior задается методом CreateDog () объекта PetFactory. Именно этот параметр вызывается для метода shed ().
Если вы сделаете это, вы обнаружите, что ваш код намного более гибкий, и большинство ваших простых объектов имеют дело с очень базовыми общесистемными поведениями.
Я рекомендую этот шаблон даже в языках множественного наследования.
в этой Java World article
лично я склонен использовать интерфейсы для определения интерфейсов-т. е. частей системного дизайна, которые определяют, как к чему-то должен быть доступ.
Это не редкость, что у меня будет класс, реализующий 1 или более интерфейсов.
абстрактные классы я использую в качестве основы для чего-то другого.
ниже приводится выдержка из вышеупомянутой статьи JavaWorld.com статья, автор Тони Синтес, 04/20/01
интерфейс и абстрактный класс
выбор интерфейсов и абстрактных классов не является предложением "или-или". Если вам нужно изменить свой дизайн, сделайте его интерфейсом. Однако у вас могут быть абстрактные классы, которые обеспечивают некоторое поведение по умолчанию. Абстрактные классы-отличные кандидаты внутри рамок приложений.
абстрактные классы позволяют определить некоторые виды поведения; они заставляют вас подклассы для предоставления других. Например, если у вас есть платформа приложений, абстрактный класс может предоставлять службы по умолчанию, такие как обработка событий и сообщений. Эти службы позволяют приложению подключаться к платформе приложений. Однако существует определенная функциональность приложения, которую может выполнять только ваше приложение. Такая функциональность может включать задачи запуска и выключения, которые часто зависят от приложения. Поэтому вместо того, чтобы пытаться определить само это поведение, абстрактный базовый класс может объявлять абстрактные методы выключения и запуска. Базовый класс знает, что ему нужны эти методы, но абстрактный класс позволяет вашему классу признать, что он не знает, как выполнять эти действия; он знает только, что он должен инициировать действия. Когда пришло время запуска, абстрактный класс может вызвать метод startup. Когда базовый класс вызывает этот метод, Java вызывает метод, определенный дочерним классом.
многие разработчики забывают, что класс определяет, что абстрактный метод также может вызывать этот метод. Абстрактные классы-отличный способ создания запланированных иерархий наследования. Они также являются хорошим выбором для классов nonleaf в иерархиях классов.
класс и интерфейс
некоторые говорят, что вы должны определить все классы в плане интерфейсов, но я думаю, что рекомендация-это слишком. Я использую интерфейсы, когда вижу, что что-то в моем дизайне будет часто меняться.
например, шаблон стратегии позволяет поменять местами новые алгоритмы и процессы в программе без изменения объектов, которые их используют. Медиаплеер может знать, как воспроизводить файлы CDs, MP3s и wav. Конечно, вы не хотите жестко закодировать эти алгоритмы воспроизведения в плеер; это затруднит добавление нового формата, такого как AVI. Кроме того, ваш код будет завален бесполезными инструкциями case. И чтобы добавить оскорбление к травме, вам нужно будет обновлять эти заявления о случаях каждый раз, когда вы добавляете новый алгоритм. В целом, это не очень объектно-ориентированного программирования.
с шаблоном стратегии вы можете просто инкапсулировать алгоритм за объектом. Если вы сделаете это, вы можете предоставить новые медиа плагины в любое время. Назовем плагин классом MediaStrategy. Этот объект будет иметь один метод: playStream (Stream s). Поэтому, чтобы добавить новый алгоритм, мы просто расширяем наш класс алгоритмов. Теперь, когда программа сталкивается с новым типом носителя, она просто делегирует воспроизведение поток к нашей медиа-стратегии. Конечно, вам понадобится сантехника, чтобы правильно создать необходимые стратегии алгоритмов.
Это отличное место для использования интерфейса. Мы использовали шаблон стратегии, который четко указывает место в дизайне, которое изменится. Таким образом, вы должны определить стратегию как интерфейс. Обычно интерфейсы предпочтительнее наследования, если требуется, чтобы объект имел определенный тип; в данном случае MediaStrategy. Доверие наследование для идентификации типа опасно; оно блокирует вас в определенной иерархии наследования. Java не допускает множественного наследования, поэтому вы не можете расширить то, что дает вам полезную реализацию или больше идентификаторов типа.
также имейте в виду, чтобы не увлекаться в OO (посмотреть блог) и всегда моделируйте объекты, основанные на требуемом поведении, если вы разрабатывали приложение, в котором единственным требуемым поведением было общее имя и вид для животного, тогда вам понадобится только одно животное класса со свойством для имени, а не миллионы классов для каждого возможного животного в мире.
У меня есть грубое эмпирическое правило
функции: вероятно, будет отличаться во всех частях: интерфейс.
данные и функциональность, части будут в основном одинаковыми, части разные: абстрактный класс.
данные и функциональность, фактически работающие, если они расширены только с небольшими изменениями: обычные (бетон) класс
сведения и функциональность, никаких изменений не планируется: обычные (бетон) класс с окончательным модификатором.
данные, и, возможно, функции: только для чтения: enum члены.
Это очень грубо и готово и совсем не строго определено, но есть спектр от интерфейсов, где все предназначено для изменения на перечисления, где все фиксируется немного как файл только для чтения.
интерфейсы должны быть небольшими. Очень маленький. Если вы действительно разбиваете свои объекты, то ваши интерфейсы, вероятно, будут содержать только несколько очень конкретных методов и свойств.
абстрактные классы являются ярлыками. Есть ли вещи, которые разделяют все производные PetBase, которые вы можете кодировать один раз и делать с ними? Если да, то пришло время для абстрактного класса.
абстрактные классы также ограничивают. Хотя они дают вам отличный ярлык для создания дочерних объектов, любой данный объект может реализовать только один абстрактный класс. Много раз я нахожу это ограничение абстрактных классов, и именно поэтому я использую много интерфейсов.
абстрактные классы могут содержать несколько интерфейсов. Ваш абстрактный класс PetBase может реализовать IPet (у домашних животных есть владельцы) и IDigestion (домашние животные едят, или, по крайней мере, они должны). Однако, PetBase, вероятно, не реализовать IMammal, поскольку не все животные-млекопитающие и не все млекопитающие животные. Вы можете добавить mammalpetbase, который расширяется PetBase и добавить Имаммал. FishBase может иметь PetBase и добавить IFish. IFish будет иметь ISwim и IUnderwaterBreather в качестве интерфейсов.
Да, мой пример сильно усложнен для простого примера, но это часть большой вещи о том, как интерфейсы и абстрактные классы работают вместе.
случай для базовых классов над интерфейсами был хорошо объяснен в руководстве по кодированию .NET Submain:
базовые классы и интерфейсы Тип интерфейса является частичным описание значения, потенциально поддерживается многими типами объектов. Использовать базовые классы вместо интерфейсов по возможности. От версии перспектива, классы более гибкие чем интерфейсы. С классом, вы можете корабль версии 1.0, а затем в версии 2.0 добавить новый метод для класса. Пока метод не является абстрактным, все существующие производные классы продолжаются функционировать без изменений.
потому что интерфейсы не поддерживают реализация наследования, шаблон, который применяется к классам делает не применяется к интерфейсам. Добавление метод к интерфейсу эквивалентен добавление абстрактного метода в базу класс; любой класс, который реализует интерфейс сломается, потому что класс не реализует новый метод. Интерфейсы соотвествующие в следующие ситуации:
- несколько несвязанных классов хотят поддерживать протокол.
- эти классы уже установили базовые классы (для образец, некоторые элементы управления пользовательского интерфейса (UI) , и некоторые из них являются веб-службами XML).
- агрегирование не подходит или практически невозможно. Во всем остальном положения, наследование классов-лучшая модель.
одно важное отличие заключается в том, что вы можете наследовать только один базовый класс, но вы можете реализовать много интерфейсы. Таким образом, вы хотите использовать базовый класс, только если вы абсолютно точно что вам не нужно будет также наследовать другой базовый класс. Кроме того, если вы обнаружите, что ваш интерфейс становится большим, вы должны начать разбивать его на несколько логических частей, которые определяют независимую функциональность, так как нет правила, что ваш класс не может реализовать их все (или что вы можете определить другой интерфейс, который наследует их все сгруппировать их).
для дальнейшего построения на примере, часто используемом в этом конкретном вопросе, есть много вещей, которые являются любимыми-подруги, автомобили, пушистые одеяла... - так что у меня, возможно, был класс Petable, который обеспечивал это общее поведение, и различные классы, наследующие от него.
однако, быть petable не является частью природы любого из этих объектов. Есть гораздо более важные понятия, которые are важно для их природы-подруга-это человек, автомобиль-наземный транспорт, кошка-млекопитающее...
поведение должно быть назначено сначала интерфейсам (включая интерфейс по умолчанию класса) и повышено до базового класса, только если они (a) являются общими для большой группы классы, которые являются подмножествами более крупного класса - в том же смысле, что" кошка "и" человек "являются подмножествами"млекопитающего".
загвоздка в том, что после того, как вы поймете объектно-ориентированный дизайн достаточно лучше, чем я сначала, вы обычно будете делать это автоматически, даже не думая об этом. Таким образом, голая истина утверждения "код интерфейса, а не абстрактный класс" становится настолько очевидной, что вам трудно поверить, что кто - то потрудился бы сказать это-и начать пытаться читать другие смысл в этом.
еще одна вещь, которую я бы добавил, что если класс чисто abstract-без неабстрактных, не унаследованных членов или методов, доступных дочернему, родительскому или клиентскому - тогда почему это класс? Он может быть заменен, в некоторых случаях интерфейсом, а в других случаях Null.
источник: http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/
C# - замечательный язык, который созрел и развился за последние 14 лет. Это отлично подходит для нас, разработчиков, потому что зрелый язык предоставляет нам множество языковых функций, которые находятся в нашем распоряжении.
однако, с большой силой становится много ответственности. Некоторые из этих функций могут быть использованы неправильно, или иногда это трудно чтобы понять, почему вы решили использовать одну функцию за другой. На протяжении многих лет я видел, как многие разработчики боролись с тем, когда использовать интерфейс или использовать абстрактный класс. Оба имеют там преимущества и недостатки и правильное время и место для использования каждого. Но как мы решим???
оба обеспечивают повторное использование общих функций между типами. Наиболее очевидным отличием сразу является то, что интерфейсы не обеспечивают реализацию для их функциональность, тогда как абстрактные классы позволяют реализовать некоторое" базовое "или" стандартное " поведение, а затем иметь возможность "переопределить" это поведение по умолчанию с производными типами классов, если это необходимо.
Это все хорошо и хорошо и обеспечивает большое повторное использование кода и придерживается Сухого (не повторяйтесь) принципа разработки программного обеспечения. Абстрактные классы отлично подходят для использования, когда у вас есть отношения" есть".
например: золотистый ретривер " - это" тип собаки. Как и пудель. Они оба умеют лаять, как и все собаки. Тем не менее, вы можете заявить, что парк пуделей значительно отличается от "стандартного" собачьего лая. Поэтому для вас может иметь смысл реализовать что-то следующее:
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
//
Как вы можете видеть, это был бы отличный способ сохранить ваш код сухим и разрешить реализацию базового класса, когда любой из типов может просто полагаться на кору по умолчанию вместо реализации специального случая. Этот такие классы, как GoldenRetriever, Boxer, Lab, могут наследовать "default" (класс bass) бесплатно только потому, что они реализуют абстрактный класс 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-метода обрабатываться в своем собственном классе.
резюме:
таким образом, мое главное эмпирическое правило-использовать абстрактный класс, когда вы хотите реализовать функциональность "по умолчанию" для иерархии классов или/и классов или типов, с которыми вы работаете, доля "является" отношением (ex. пудель "является" типом собаки).
С другой стороны, используйте интерфейс, когда у вас нет "is a" отношения, но имеют типы, которые разделяют "способность" что-то делать или иметь что-то (например. Утка " не " человек. Однако утка и человек делятся "умением" плавать).
еще одно различие между абстрактными классами и интерфейсами заключается в том, что класс может реализовать один для многих интерфейсов, но класс может наследовать только от одного абстрактного класса (или любого класса, если на то пошло). Да, вы можете вложить классы и иметь иерархию наследования (что многие программы делают и должны иметь), но нельзя наследовать два класса в одном определении производного класса (это правило применяется к C#. На некоторых других языках вы можете это сделать, как правило, только из-за отсутствия интерфейсов на этих языках).
также помните при использовании интерфейсов придерживаться принципа сегрегации интерфейса (ISP). ISP заявляет, что ни один клиент не должен зависеть от методов, которые он не использует. По этой причине интерфейсы должны быть ориентированы на конкретные задачи и обычно очень малы (например. Интерфейс IDisposable, Интерфейс Icomparable ).
еще один совет, если вы разрабатываете небольшой, лаконичный функций, использовать интерфейсы. Если вы разрабатываете большие функциональные единицы, используйте абстрактный класс.
надеюсь, что это проясняет ситуацию для некоторых людей!
также, если вы можете придумать лучшие примеры или хотите что-то указать, пожалуйста, сделайте это в комментариях ниже!
предыдущие комментарии об использовании абстрактных классов для общей реализации определенно на отметке. Одно из преимуществ, о котором я еще не упоминал, заключается в том, что использование интерфейсов значительно упрощает реализацию макетных объектов для модульного тестирования. Определение IPet и PetBase, как описал Джейсон Коэн, позволяет легко издеваться над различными условиями данных, без накладных расходов физической базы данных (пока вы не решите, что пришло время проверить реальную вещь).
Не используйте базовый класс, если вы не знаете, что это значит, и что это применимо в этом случае. Если это применимо, используйте его, в противном случае используйте интерфейсы. Но обратите внимание на ответ о небольших интерфейсах.
Public Inheritance чрезмерно используется в OOD и выражает гораздо больше, чем большинство разработчиков понимают или готовы жить. Вижу Принцип Лисков Substitutablity
короче говоря, если A "является A" B, то A требует не более B и поставляет не менее B, для каждый метод, который он раскрывает.
концептуально интерфейс используется для формального и полуформального определения набора методов, которые будет предоставлять объект. Формально означает набор имен и подписей методов, полуформально означает читаемую человеком документацию, связанную с этими методами. Интерфейсы - это только описания API (в конце концов, API означает Application Programmer интерфейс), они не могут содержать никакой реализации, и невозможно использовать или запускать интерфейс. Они только делают явным контракт, как вы должны взаимодействовать с объектом.
классы предоставляют реализацию, они могут объявить, что они реализуют ноль, один или несколько интерфейсов. Если класс предназначен для наследования, соглашение заключается в префиксе имени класса с помощью "Base".
существует различие между базовым классом и абстрактными базовыми классами (ABC). ABCs смешивает интерфейс и реализацию вместе. Аннотация вне компьютерного программирования означает "резюме", то есть " аннотация == Взаимодействие." Абстрактный базовый класс может описывать как интерфейс,так и пустую, частичную или полную реализацию, которая должна быть унаследована.
мнения о том, когда использовать интерфейсы по сравнению с абстрактными базовыми классами по сравнению с просто классами, будут сильно отличаться в зависимости от того, что вы разрабатываете, и на каком языке вы разрабатываете. Интерфейсы часто связаны только со статически типизированными языками, такими как Java или C#, но динамически типизированные языки также могут имеют интерфейсы и абстрактные базовые классы. В Python, например, различие между классом, который объявляет, что он осуществляет интерфейс, и объект, который является экземпляром класса, и сказал, чтобы обеспечить этого интерфейса. В динамическом языке возможно, что два объекта, являющиеся экземплярами одного класса, могут объявить, что они предоставляют полностью разные интерфейсы. В Python это возможно только для объекта атрибуты, в то время как методы являются общим состоянием между всеми объектами класса. Однако в Ruby объекты могут иметь методы для каждого экземпляра, поэтому возможно, что интерфейс между двумя объектами одного класса может варьироваться столько, сколько пожелает программист (однако Ruby не имеет явного способа объявления интерфейсов).
в динамических языках интерфейс к объекту часто подразумевается неявно, либо путем интроспекции объекта и запроса его методов, которые он предоставляет (см. Вы прыгаете) или, предпочтительно, просто пытаясь использовать желаемый интерфейс на объекте и ловить исключения, если объект не предоставляет этот интерфейс (проще попросить прощения, чем разрешение). Это может привести к" ложным срабатываниям", когда два интерфейса имеют одинаковое имя метода, но семантически отличаются, однако компромисс заключается в том, что ваш код более гибкий, поскольку вам не нужно указывать заранее, чтобы предвидеть все возможные использования вашего кода.
другой вариант, который нужно иметь в виду,-это использование отношения "has-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); }
}
Да, этот пример показывает, что существует много дублирования кода и отсутствие элегантности поступая таким образом. Но следует также понимать, что это помогает держать собаку и кошку отделенными от класса домашних животных (в этом Собака и кошка не имеют доступа к частным членам домашнего животного), и это оставляет место для собаки и кошки, чтобы унаследовать что-то еще-возможно, класс млекопитающих.
композиция предпочтительнее, когда нет частного доступа, и вам не нужно ссылаться на собаку и кошку, используя общие ссылки/указатели на домашних животных. Интерфейсы дают вам эту общую ссылку возможность и может помочь сократить многословность кода, но они также могут запутать вещи, когда они плохо организованы. Наследование полезно, когда вам нужен доступ к частному члену, и при его использовании вы обязуетесь высоко связывать свои классы собак и кошек с вашим классом домашних животных, что является крутой стоимостью для оплаты.
между наследованием, композицией и интерфейсами нет одного способа, который всегда прав, и это помогает рассмотреть, как все три варианта могут быть использованы в гармония. Из трех вариантов наследование обычно является вариантом, который следует использовать реже всего.
предпочитают интерфейсы абстрактным классам
обоснование, основные моменты для рассмотрения [два уже упомянутых здесь]:
- интерфейсы более гибкие, потому что класс может реализовать несколько межфазные границы. Поскольку в Java нет множественного наследования, используя абстрактные классы не позволяют пользователям использовать другие классы иерархия. В общем, предпочитайте интерфейсы, когда нет значения по умолчанию реализации или состояние. Java коллекции предлагают хорошие примеры это (карта, набор и т. д.).
- абстрактные классы имеют преимущество, позволяя лучше вперед совместимость. Как только клиенты используют интерфейс, вы не можете его изменить; если они используют абстрактный класс, вы все равно можете добавить поведение без нарушение существующего кода. если совместимость является проблемой, рассмотрите возможность использования абстрактный класс.
- даже если у вас есть реализации по умолчанию или внутреннее состояние,
рассмотрим предложение интерфейса и абстрактная реализация его.
Это поможет клиентам, но все же позволит им большую свободу, если
нужные [1].
Конечно, эта тема была подробно обсуждена. в другом месте [2,3].
[1] это добавляет больше кода, конечно, но если краткость является вашей главной заботой, вы, вероятно, должны были избежать Java в первую очередь!
[2] Джошуа блох эффективная Java, пункты 16-18.
Это зависит от ваших требований. Если IPet достаточно прост, я бы предпочел реализовать это. В противном случае, если PetBase реализует тонну функций, которые вы не хотите дублировать, то имейте это.
недостатком реализации базового класса является требование override
(или new
) существующие методы. Это делает их виртуальными методами, что означает, что вы должны быть осторожны в использовании экземпляра объекта.
наконец, одно наследование .NET убивает мне. Наивный пример: скажем, вы создаете пользовательский элемент управления, поэтому вы наследуете UserControl
. Но теперь вы заблокированы от наследования PetBase
. Это заставляет вас реорганизоваться, например, сделать PetBase
- члена класса, а не.
Я обычно не реализую ни один, пока мне не понадобится. Я предпочитаю интерфейсы абстрактным классам, потому что это дает немного больше гибкости. Если есть общее поведение в некоторых наследующих классах, я перемещаю это и делаю абстрактный базовый класс. Я не вижу необходимости в обоих, так как они по существу служат одной и той же цели, и оба имеют плохой запах кода (imho), что решение было чрезмерно спроектировано.
Что касается 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();
}
теперь, поскольку у всех млекопитающих есть волосы и соски (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)
}
этот дизайн хорош, когда есть много конкретных классов, и вы не хотите поддерживать шаблон только для программирования интерфейса. Если бы в интерфейс были добавлены новые методы, это нарушило бы все результирующие классы, поэтому вы все еще получаете преимущества интерфейсного подхода.
в этом случае абстрактное может быть также конкретным; однако абстрактное обозначение помогает подчеркнуть, что этот шаблон частный предприниматель.
наследник базового класса должен иметь отношение" is a". Интерфейс представляет собой отношение" реализует". Поэтому используйте только базовый класс, когда ваши наследники будут поддерживать отношения is.
используйте интерфейсы для принудительного исполнения контракта между семействами несвязанных классов. Например, у вас могут быть общие методы доступа для классов, которые представляют коллекции, но содержат радикально разные данные, т. е. один класс может представлять результирующий набор из запроса, а другой может представлять изображения в галерее. Кроме того, вы можете реализовать несколько интерфейсов, что позволит вам смешивать (и обозначать) возможности класса.
использовать наследование, когда классы несут общие отношения и, следовательно, имеют схожую структурную и поведенческую сигнатуру, т. е. автомобиль, мотоцикл, грузовик и внедорожник-это все типы дорожных транспортных средств, которые могут содержать несколько колес, максимальную скорость