Джерело : http://jasonroell.com/2014/12/09/interfaces-vs-abrief-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). ISP заявляє, що жоден клієнт не повинен змушувати залежати від методів, які він не використовує. З цієї причини інтерфейси повинні бути зосереджені на конкретних завданнях і зазвичай дуже малі (наприклад, IDisposable, IComparable).
Ще одна порада - якщо ви розробляєте невеликі, стислі шматочки функціональності, використовуйте інтерфейси. Якщо ви проектуєте великі функціональні одиниці, використовуйте абстрактний клас.
Сподіваюсь, це дещо прояснить справи!
Також якщо ви можете придумати кращі приклади або хочете щось зазначити, будь ласка, зробіть це в коментарях нижче!