Интерфейс против Базового класса - PullRequest
743 голосов
/ 11 сентября 2008

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

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

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

Ответы [ 39 ]

12 голосов
/ 17 октября 2008

Объяснено хорошо в этой статье о мире Java

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

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

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

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


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

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

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

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

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

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

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

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

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

10 голосов
/ 15 сентября 2008

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

9 голосов
/ 14 октября 2012

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

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

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

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

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

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

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

7 голосов
/ 11 сентября 2008

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

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

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

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

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

6 голосов
/ 18 ноября 2008

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// 

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

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

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

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

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

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

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

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

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

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

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

      var listOfThingsThatCanSwim = new List<ISwimable>();

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

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

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

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

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

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

Резюме:

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

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

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

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

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

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

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

5 голосов
/ 11 сентября 2008

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

4 голосов
/ 27 февраля 2016

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

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

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

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

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

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

4 голосов
/ 12 декабря 2008

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

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

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

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

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

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

3 голосов
/ 12 сентября 2008

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

...