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

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

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

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


person Community    schedule 11.09.2008    source источник
comment
Я думаю, вы должны принять во внимание одну вещь - интерфейсы могут иметь несколько ограничений, о которых вы можете не знать до самых поздних стадий. Например, с .NET вы не можете сериализовать переменную-член интерфейса, поэтому, если у вас есть класс Zoo и массив переменных-членов IAnimals, вы не сможете сериализовать Zoo (а это означает, что написание WebServices или других вещей, требующих сериализации, будет боль).   -  person synhershko    schedule 19.07.2009
comment
Этот вопрос может помочь понять концепцию интерфейсов. stackoverflow.com/q/8531292/1055241   -  person gprathour    schedule 07.07.2014
comment
Мне просто интересно. Я встретил в CLR via C # следующий отрывок: I tend to prefer using the interface technique over the base type technique because the base type technique doesn’t allow the developer to choose the base type that works best in a particular situation.. Я не могу понять, что имеется в виду в отрывке. Мы можем создать несколько базовых типов и создать производный тип для любого из них, чтобы разработчик мог выбрать базовый тип. Может кто-нибудь объяснить, пожалуйста, что мне не хватает? Я считаю, что это может быть частью этого вопроса. Или я должен опубликовать еще один о конкретном отрывке?   -  person qqqqqqq    schedule 06.03.2020


Ответы (38)


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

person Community    schedule 11.09.2008
comment
Я не думаю, что это так просто. Вы немного изменили вопрос (требования), чтобы интерфейс стал более понятным. Вы всегда должны спрашивать себя, определяете ли вы контракт (интерфейс) или общую реализацию (базовый класс). - person David Pokluda; 11.09.2008
comment
Интерфейс - это контракт. Вы раскрываете только ту часть контракта, которая требуется службе. Если у вас есть «PettingZoo», вы, конечно же, не захотите раскрывать «Спаривание» пользователю! - person Anthony Mastrean; 11.09.2008
comment
@David Touche, хотя я сделал это, чтобы лучше проиллюстрировать, для чего нужен интерфейс и что такое абстрактный класс для его понимания. Собака и кошка не являются жесткими требованиями! - person Jon Limjap; 12.09.2008
comment
@Jon Вау, мне нравится ваше решение проблемы с контактным зоопарком - person smaclell; 18.10.2008
comment
@DaveRook LOL! Отсюда, наверное, мой классификатор. Feed и Mate выглядят корректно, возможно, Hunt должен быть в другом интерфейсе, таком как IPredator или ICarnivore: D - person Jon Limjap; 29.01.2014
comment
Следует отметить, что в интерпретируемых средах JIT (особенно JVM) вызовы виртуальных методов выполняются значительно быстрее, чем вызовы методов интерфейса, в зависимости от контекста. Я подчеркиваю контекст, потому что JVM часто может оптимизировать медленный поиск методов. (например, если список наследников интерфейса, как правило, является экземплярами одного и того же класса. Это, к сожалению, немного затрудняет тестирование.) Если вы пытаетесь оптимизировать что-то, чувствительное к производительности, и только тогда вы должны учитывать это. - person Philip Guin; 23.02.2014
comment
Кроме того, интерфейс позволяет использовать камень для питомца, позволяя его купать и обучать трюкам, но кормление и спаривание не поддерживаются, потому что это было бы смешно для камня. - person eclux; 09.12.2015
comment
@eclux Ага, разве это не круто? : D - person Jon Limjap; 10.12.2015

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

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

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

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

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

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

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

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

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

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

person Community    schedule 11.09.2008
comment
Всякий раз, когда необходимо внести изменение в интерфейс, самый надежный способ сделать это - создать новый интерфейс, унаследованный от старого. Это сохраняет существующие реализации и позволяет вам делать все, что вы хотите, в новой. - person Scott Lawrence; 12.09.2008
comment
у нас есть принцип разделения интерфейса. Этот принцип учит нас заботиться о том, как мы пишем наши интерфейсы. Когда мы пишем наши интерфейсы, мы должны заботиться о добавлении только тех методов, которые должны быть там. Если мы добавим методы, которых не должно быть, классы, реализующие интерфейс, также должны будут реализовать эти методы. Например, если мы создадим интерфейс под названием Worker и добавим перерыв на обед для метода, всем рабочим придется его реализовать. Что, если рабочий - робот? В заключение, интерфейсы, содержащие методы, не относящиеся к нему, называются загрязненными или жирными интерфейсами. - person Eklavyaa; 21.07.2016
comment
Начиная с Java 8, методы по умолчанию позволяют добавлять новые функции к интерфейсам и обеспечивать обратную совместимость для существующих классов, реализующих этот интерфейс. По умолчанию вызываются методы по умолчанию, если они не переопределены в реализующем классе. Все реализующие классы могут либо переопределять методы по умолчанию, либо вызывать их напрямую с помощью instance.defaultMethod () - person Arpit Rastogi; 12.10.2017

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

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

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

person Community    schedule 15.09.2008
comment
Из всех ответов этот дает лучшую смесь краткости без потери ясности. - person Matt Wilko; 17.04.2014
comment
Кто-то однажды сказал мне использовать интерфейс, когда есть связь. Я не уверен, всегда ли это правда; У моего ноутбука есть экран, поэтому должен ли ноутбук использовать IScreen или иметь свойство Screen? Последнее мне кажется более естественным. - person Berend; 30.10.2014
comment
@berend смешно, что ты пишешь, потому что экраны реализованы через интерфейсы - VGA, HDMI и т. д. - person Konstantin; 16.12.2014
comment
Просто потому, что это ООП, мы должны расширять свое воображение, чтобы следовать сценариям из реальной жизни. Это не всегда применимо. - person Crismogram; 11.04.2018

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

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

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

person Community    schedule 11.09.2008
comment
есть свой торт и съесть его тоже! - person Darren Kopp; 11.09.2008
comment
да да! А с современными IDE он даже напишет для вас шаблонный код (а некоторым языкам это даже не нужно) - person Jason Cohen; 11.09.2008
comment
Интерфейс определяет, как другие классы могут использовать ваш код. Базовый класс помогает разработчикам реализовать ваш интерфейс. Две разные вещи для двух разных целей. - person Bill Michell; 04.11.2008
comment
Я нахожу это сомнительным: это совет .NET? У вас есть ссылка? В принципе, есть несколько причин, по которым вы должны создать базовый класс, обычно делегирование предпочтительнее наследования. - person andygavin; 04.06.2009
comment
Например, чтобы создать класс потока в Java, вы можете либо расширить java.lang.Thread , либо реализовать java.lang.Runnable. Чтобы создать сервер RMI, вы можете либо расширить java.rmi.server.UnicastRemoteObject , либо реализовать java.rmi.Remote и вызвать UnicastRemoteObject.exportObject(). - person Kevin Panko; 20.05.2010
comment
Здесь нет ничего современного. Базовый класс и интерфейс с одним и тем же API просто избыточны. Вы можете использовать этот подход в некоторых случаях, но не следует обобщать! - person smentek; 20.08.2011
comment
Самый поддерживаемый комментарий ко второму поддерживаемому ответу на самом деле не согласуется с самим ответом, интересно. Я должен сказать, что вижу много примеров сосуществования интерфейса и базового класса. В этом смысле это современный способ. Например, в шаблоне MVVM на самом деле есть класс ViewModelBase, который реализует INotifyPropertyChanged. Но когда мой коллега спросил меня, почему у меня базовый класс, вместо того, чтобы реализовывать интерфейс в каждой модели представления, я не знаю, как его убедить - person tete; 25.09.2012
comment
Верный. Дело не в одном или другом. Они существуют для решения двух очень разных проблем. Интерфейсы - это контракты, к которым должен обращаться реализующий класс. В последнее время они нашли поддержку (иногда фанатично) для аспектов IoC и TDD. Абстрактные / базовые классы служат для иерархической группировки общей логики и свойств. Это сокращает количество дублированного кода, что, в свою очередь, увеличивает ремонтопригодность решения и снижает его подверженность ошибкам. - person ComeIn; 09.08.2018
comment
Avoid именование базовых классов с суффиксом Base, если класс предназначен для использования в общедоступных API. Рекомендации по проектированию каркаса, стр. 174 - person Yousha Aleayoub; 15.05.2020

Интерфейсы

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

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

  • Позволяет добавить некоторую реализацию по умолчанию, которую вы получаете бесплатно путем создания
  • За исключением C ++, вы можете быть производным только от одного класса. Даже если бы мог из нескольких классов, это обычно плохая идея.
  • Изменить базовый класс относительно просто. От выводов ничего особенного делать не нужно
  • Базовые классы могут объявлять защищенные и общедоступные функции, к которым можно получить доступ производными
  • Абстрактные базовые классы не так легко подделать, как интерфейсы
  • Базовые классы обычно указывают иерархию типов (IS A)
  • Происхождение классов может зависеть от некоторого базового поведения (иметь сложные знания о родительской реализации). Все может быть запутано, если вы измените базовую реализацию для одного человека и сломаете остальные.
person Community    schedule 11.09.2008
comment
Примечание: в руководящих принципах разработки фреймворка рекомендуется использовать базовые классы (в отличие от интерфейсов), потому что они лучше версии. Добавление нового метода к абстрактному базовому классу в vNext не является критическим изменением. - person Gishu; 30.12.2010

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

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

person Community    schedule 11.09.2008
comment
Ага. Лично я считаю, что это задница задом наперед. Интерфейсы должны содержать минимум функциональных возможностей для типа, а базовые классы должны обеспечивать богатую структуру, на которой может быть построена настройка. Поместите это в интерфейс, и вам будет очень сложно его реализовать. - person ; 11.09.2008
comment
Никто не говорил, что ваши интерфейсы должны быть огромными. Меньшие по размеру, множественные интерфейсы и более богатые базовые классы составляют отличный API. - person Kilhoffer; 11.09.2008
comment
Это только у меня, или у большинства обычных рабочих классов есть общая реализация? В этом контексте это противоречит вашему общему правилу отдавать предпочтение интерфейсам. Я бы переформулировал ваше обобщение в виде двух обобщений: те общие классы, которые не содержат или не содержат логики, должны реализовывать интерфейс. Те общие классы, которые содержат приличный объем логики, должны быть производными от базового класса (поскольку, скорее всего, они будут иметь общие функции). - person goku_da_master; 10.07.2012
comment
Интерфейсы @Kilhoffer обеспечивают максимальную гибкость и переносимость типов через границы, пожалуйста, уточните это утверждение. - person JAVA; 04.11.2014

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

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

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

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

person Community    schedule 11.09.2008

Хуан,

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

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

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

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

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

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

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

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

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

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

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

person Community    schedule 11.09.2008

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

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

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

person Community    schedule 15.10.2008

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

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

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

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

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

person Community    schedule 11.09.2008

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

person Community    schedule 15.09.2008

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

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

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

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

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

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

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

person Community    schedule 14.10.2012

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

// 

Как видите, это был бы отличный способ сохранить ваш код 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'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 (человек), ShowHowDuckSwims (утка) и т. Д.)

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

Резюме:

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

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

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

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

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

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

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

person Community    schedule 09.12.2014

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

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

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

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

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

person Community    schedule 11.09.2008

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

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

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

  1. Несколько несвязанных классов хотят поддерживать протокол.
  2. Эти классы уже имеют установленные базовые классы (например, некоторые из них являются элементами управления пользовательского интерфейса (UI), а некоторые - веб-службами XML).
  3. Агрегация нецелесообразна и нецелесообразна. Во всех других ситуациях наследование классов - лучшая модель.
person Community    schedule 18.11.2008
comment
Я чувствую, что этому ответу следует уделить больше внимания. Здесь это идет вразрез со многими ответами. Я бы не сказал, что полностью согласен, но здесь есть важные моменты. - person kayleeFrye_onDeck; 19.12.2015

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

person Community    schedule 11.09.2008

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

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

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

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

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

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

person Community    schedule 11.12.2008
comment
Чисто абстрактный класс может обеспечивать поведение методов по умолчанию. Это полезно, когда все ваши конкретные классы будут использовать общие методы, которые будут избыточными для повторной реализации снова и снова. - person Adam Hughes; 04.03.2016

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

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

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

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

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

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

person Community    schedule 27.02.2016

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

person Community    schedule 11.09.2008

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

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

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

person Community    schedule 12.09.2008

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

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

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

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

person Community    schedule 03.11.2008

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

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

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

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

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

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

person Community    schedule 15.09.2008

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

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

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

person Community    schedule 11.09.2008

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

person Community    schedule 11.09.2008

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

person Community    schedule 23.02.2011

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

person Community    schedule 23.09.2014

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

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

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

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

person Community    schedule 04.03.2016

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

person Community    schedule 11.09.2008

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

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

person Community    schedule 14.03.2011

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

Например, ваша PetBase должна дышать, а ваш IPet может DoTricks.

Анализ вашей проблемной области поможет вам определить точную иерархическую структуру.

person Community    schedule 23.01.2015

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

Вы должны использовать интерфейс, если

  1. У вас есть чистые abstract методы и нет не абстрактных методов
  2. У вас нет реализации по умолчанию для non abstract методов (за исключением языка Java 8, где методы интерфейса обеспечивают реализацию по умолчанию)
  3. Если вы используете Java 8, теперь интерфейс будет предоставлять реализацию по умолчанию для некоторых неабстрактных методов. Это сделает interface более удобным в использовании по сравнению с abstract классами.

Взгляните на этот SE вопрос, чтобы узнать подробнее.

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

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

Пример на java:

abstract class PetBase implements IPet {
// Add all abstract methods in IPet interface and keep base class clean. 
   Base class will contain only non abstract methods and static methods.
}

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

Я предпочитаю, чтобы интерфейс реализовывал базовый класс.

 abstract class PetBase implements IPet {
 // Add all abstract methods in IPet
 }

 /*If ISheds,IBarks is common for Pets, your PetBase can implement ISheds,IBarks. 
  Respective implementations of PetBase can change the behaviour in their concrete classes*/

 abstract class PetBase implements IPet,ISheds,IBarks {
 // Add all abstract methods in respective interfaces
 }

Преимущества:

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

  2. Вы можете обеспечить неизменность базового класса через интерфейс. Прочтите эту статью

Обратитесь к соответствующему вопросу SE для получения более подробной информации:

Как следует Я объяснил разницу между интерфейсом и абстрактным классом?

person Community    schedule 05.01.2016

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

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

person Community    schedule 11.09.2008

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

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

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

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

person Community    schedule 11.09.2008

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

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

person Community    schedule 11.09.2008

У наследования есть и другие преимущества, такие как способность переменной содержать объект либо родительского, либо унаследованного класса (без необходимости объявлять его как нечто общее, например «Object»).

Например, в .NET WinForms большинство компонентов пользовательского интерфейса являются производными от System.Windows.Forms.Control, поэтому переменная, объявленная так, может «содержать» практически любой элемент пользовательского интерфейса - будь то кнопка, ListView или что-то еще. Теперь, конечно, у вас не будет доступа ко всем свойствам или методам элемента, но у вас будут все основные вещи - и это может быть полезно.

person Community    schedule 11.09.2008
comment
Ваш пример не поддерживает эту концепцию. То же самое можно сделать и с интерфейсом. Для этого не обязательно иметь базовый класс. - person Kilhoffer; 12.09.2008

Вам следует использовать базовый класс, если у других разработчиков действительно нет причин желать использовать свой собственный базовый класс в дополнение к членам вашего типа и вы предвидите проблемы с версией (см. http://haacked.com/archive/2008/02/21/versioning-issues-with-abstract-base-classes-and-interfaces.aspx).

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

person Community    schedule 12.09.2008

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

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

Адаптировано из книги Pro C # 5.0 и .NET 4.5 Framework.

Тип интерфейса может показаться очень похожим на абстрактный базовый класс. Напомним, что когда класс помечен как абстрактный, он может определять любое количество абстрактных членов, чтобы обеспечить полиморфный интерфейс для всех производных типов. Однако, даже когда класс действительно определяет набор абстрактных членов, он также может определять любое количество конструкторов, данных поля, неабстрактных членов (с реализацией) и так далее. С другой стороны, интерфейсы содержат только абстрактные определения членов. Полиморфный интерфейс, установленный абстрактным родительским классом, страдает одним серьезным ограничением в том, что только производные типы поддерживают члены, определенные абстрактным родительским классом. Однако в более крупных программных системах очень часто разрабатывается несколько иерархий классов, у которых нет общего родительского элемента, кроме System.Object. Учитывая, что абстрактные члены в абстрактном базовом классе применяются только к производным типам, у нас нет возможности настроить типы в разных иерархиях для поддержки одного и того же полиморфного интерфейса. В качестве примера предположим, что вы определили следующий абстрактный класс:

public abstract class CloneableType
{
// Only derived types can support this
// "polymorphic interface." Classes in other
// hierarchies have no access to this abstract
// member.
   public abstract object Clone();
}

Учитывая это определение, только члены, расширяющие CloneableType, могут поддерживать метод Clone (). Если вы создадите новый набор классов, которые не расширяют этот базовый класс, вы не сможете получить этот полиморфный интерфейс. Также вы можете вспомнить, что C # не поддерживает множественное наследование классов. Следовательно, если вы хотите создать MiniVan, который является автомобилем и является CloneableType, вы не сможете этого сделать:

// Nope! Multiple inheritance is not possible in C#
// for classes.
public class MiniVan : Car, CloneableType
{
}

Как вы уже догадались, на помощь приходят типы интерфейсов. После определения интерфейса он может быть реализован любым классом или структурой, в любой иерархии, в любом пространстве имен или любой сборке (написанной на любом языке программирования .NET). Как видите, интерфейсы очень полиморфны. Рассмотрим стандартный интерфейс .NET с именем ICloneable, определенный в пространстве имен System. Этот интерфейс определяет единственный метод с именем Clone ():

public interface ICloneable
{
object Clone();
}
person Community    schedule 29.08.2014

person    schedule
comment
+1. Опираться на наследование для идентификации типа опасно; он блокирует вас в определенной иерархии наследования. Это предложение прекрасно описывает причины, по которым я предпочитаю интерфейсы. - person Engineer; 31.05.2011
comment
И помимо этого, вместо того, чтобы расширять, составьте реализацию каждого метода вашего интерфейса. - person Engineer; 31.05.2011