Источник : 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).
Еще один совет: если вы разрабатываете небольшие, лаконичные кусочки функциональности, используйте интерфейсы. Если вы разрабатываете большие функциональные блоки, используйте абстрактный класс.
Надеюсь, это прояснит ситуацию для некоторых людей!
Также, если вы можете придумать какие-нибудь лучшие примеры или хотите указать что-то, пожалуйста, сделайте это в комментариях ниже!