Як правило, правило звучить приблизно так:
- Спадкування описаний аналіз є різновидом відносин.
- Реалізація інтерфейсу описує взаємозв'язок, що може зробити .
Щоб дещо конкретніше сказати, давайте розглянемо приклад. System.Drawing.Bitmap
Клас є-ан зображень (і як такий, він успадковує від Image
класу), але вона також може робити- утилізації, тому він реалізує IDisposable
інтерфейс. Він також може виконувати серіалізацію, тому реалізується з ISerializable
інтерфейсу.
Але більш практично, інтерфейси часто використовуються для імітації множинного успадкування в C #. Якщо ваш Processor
клас повинен успадкувати щось на зразок System.ComponentModel.Component
, то вам залишається лише вибір, як реалізувати IProcessor
інтерфейс.
Справа в тому, що як інтерфейси, так і абстрактний базовий клас надають контракт, який вказує, що може робити конкретний клас. Поширений міф, що для оголошення цього контракту необхідні інтерфейси, але це не правильно. На мою думку, найбільшою перевагою є те, що абстрактні базові класи дозволяють вам надавати функціональність за замовчуванням для підкласів. Але якщо немає функціоналу за замовчуванням, який має сенс, ніщо не заважає вам позначати сам метод якabstract
, що вимагає, щоб похідні класи реалізовували його самі, як якщо б вони реалізовували інтерфейс.
Щоб отримати відповіді на подібні запитання, я часто звертаюся до Керівних принципів проектування .NET Framework , де сказано про вибір між класами та інтерфейсами:
Загалом класи є найкращою конструкцією для викриття абстракцій.
Основним недоліком інтерфейсів є те, що вони набагато менш гнучкі, ніж класи, коли йдеться про можливість еволюції API. Після того, як ви надіслали інтерфейс, набір його членів закріплюється назавжди. Будь-які доповнення до інтерфейсу порушують існуючі типи, що реалізують інтерфейс.
Клас пропонує набагато більшу гнучкість. Ви можете додавати учасників до класів, які ви вже відправили. Поки метод не є абстрактним (тобто, поки ви надаєте реалізацію методу за замовчуванням), будь-які існуючі похідні класи продовжують функціонувати без змін.
[. . . ]
Одним з найпоширеніших аргументів на користь інтерфейсів є те, що вони дозволяють відокремлювати контракт від реалізації. Однак аргумент неправильно передбачає, що ви не можете відокремити контракти від реалізації за допомогою класів. Абстрактні класи, що знаходяться в окремій збірці від їх конкретних реалізацій, є чудовим способом досягнення такого розділення.
Їх загальні рекомендації такі:
- Є сприяють визначення класів через інтерфейси.
- Як використовувати абстрактні класи замість інтерфейсів роз'єднати контракт від реалізації. При правильному визначенні абстрактних класів допускається однаковий ступінь розмежування між контрактом та виконанням.
- Як визначити інтерфейс , якщо вам потрібно , щоб забезпечити поліморфну ієрархію типів значень.
- Подумайте про визначення інтерфейсів для досягнення ефекту, подібного до ефекту багаторазового успадкування.
Кріс Андерсон висловлює особливу згоду з цим останнім принципом, аргументуючи, що:
Абстрактні типи роблять версію набагато кращою і дозволяють розширюватися в майбутньому, але вони також спалюють ваш єдиний базовий тип. Інтерфейси доречні, коли ви дійсно визначаєте контракт між двома об'єктами, який інваріантний з часом. Абстрактні базові типи краще визначати загальну основу для сімейства типів.