OOP Course
Сьогодні

Підрозділ 7.2

Застосування інтерфейсів

Пояснює застосування інтерфейсів у класах і структурах, реалізацію за замовчуванням, множинну реалізацію та перетворення між класами й інтерфейсами.

7.2. Застосування інтерфейсів

Інтерфейс являє собою певний опис типу — набір компонентів, який повинен мати тип даних, що реалізує цей інтерфейс. Принципова відмінність від класу: ми не можемо створювати об'єкти інтерфейсу безпосередньо через конструктор. Інтерфейс — це не клас, у нього немає конструктора і немає тіла для розміщення стану:

IDiagnosable d = new IDiagnosable(); // ! Помилка — інтерфейс не можна інстанціювати

interface IDiagnosable
{
    void RunDiagnostics();
}

Натомість інтерфейс призначений для реалізації у класах і структурах. Змінна типу інтерфейсу може зберігати посилання на будь-який об'єкт, клас якого реалізує цей інтерфейс. Саме це і є ключовою перевагою: код, що працює з IDiagnosable, не знає — і не повинен знати — чи це Patient, LabSample, чи MedicalDevice. Він знає лише, що об'єкт гарантовано має метод RunDiagnostics().

Реалізація інтерфейсу у класі та структурі

Для застосування інтерфейсу після імені класу або структури через двокрапку вказується ім'я інтерфейсу — так само, як при успадкуванні. Клас зобов'язаний реалізувати всі методи та властивості інтерфейсу, якщо вони не мають реалізації за замовчуванням. Невиконання цієї умови — помилка компіляції.

Якщо методи та властивості інтерфейсу оголошені без модифікатора доступу, вони вважаються public. При реалізації в класі або структурі до них можна застосовувати виключно модифікатор public — зменшити рівень доступу неможливо, адже інтерфейс є публічним контрактом.

Інтерфейс може реалізовувати не лише клас, а й структура. Для структури це особливо важливо: вона не може успадковувати клас, але може реалізовувати будь-яку кількість інтерфейсів:

Інтерфейс як параметр методу — поліморфізм

Найпрактичніше застосування інтерфейсів — передача як параметра методу. Метод, що приймає IDiagnosable, може працювати з будь-яким об'єктом, що реалізує цей інтерфейс: класом, структурою, або будь-яким майбутнім типом, який ще не написаний. На момент написання такого методу достатньо знати лише контракт — що у переданого об'єкта є метод RunDiagnostics().

Це і є поліморфізм через інтерфейс: один метод обробляє різні типи однаково, через спільний контракт:

Метод ProcessDiagnostics не має жодної залежності від Patient чи LabSample. Інтерфейс — це контракт, що певний тип обов'язково реалізує деякий функціонал. Якщо завтра з'явиться новий тип MedicalDevice : IDiagnosable, метод ProcessDiagnostics одразу зможе з ним працювати без жодних змін.

Реалізація за замовчуванням

Починаючи з C# 8.0, інтерфейси підтримують реалізацію методів і властивостей за замовчуванням. Це вирішує важливу практичну проблему: якщо опублікований інтерфейс використовують десятки класів у різних бібліотеках, додавання нового абстрактного методу зламає всі ці класи — вони перестануть компілюватись. Замість цього новий метод можна додати з реалізацією за замовчуванням: усі наявні реалізатори продовжать працювати, а ті, кому потрібна специфічна поведінка, перевизначать метод самостійно.

Варто зазначити важливий нюанс: якщо змінна оголошена типом конкретного класу (а не інтерфейсу), виклик default-методу через неї неможливий, якщо клас його не перевизначив. Default-реалізація доступна лише через змінну типу інтерфейсу:

Множинна реалізація інтерфейсів

В C# клас може успадковувати лише один базовий клас, але реалізовувати будь-яку кількість інтерфейсів. Це дозволяє описати різні ролі одного об'єкта незалежно одна від одної. Реальний клінічний об'єкт — наприклад, пацієнт — одночасно підлягає діагностиці, виставленню рахунків і отриманню сповіщень. Кожна ця роль виражається своїм інтерфейсом, і клас Patient реалізує їх усі:

class Patient : IDiagnosable, IBillable, INotifiable
{
    // ...
}

Усі реалізовані інтерфейси перераховуються через кому. Якщо клас одночасно успадковує базовий клас і реалізує інтерфейси, базовий клас вказується першим:

class Patient : BaseRecord, IDiagnosable, IBillable, INotifiable
{
    // ...
}

Розглянемо повний клінічний приклад із трьома інтерфейсами:

Клас реалізує кілька інтерфейсів

Інтерфейси у перетвореннях типів

Все сказане щодо перетворення типів між класами стосується і інтерфейсів. Оскільки клас Patient реалізує інтерфейс IDiagnosable, змінна типу IDiagnosable може зберігати посилання на об'єкт типу Patient. Таке перетворення від конкретного класу до інтерфейсу є розширювальним (widening) і відбувається автоматично — воно завжди безпечне, бо будь-який Patient гарантовано є IDiagnosable:

Зворотне перетворення — від інтерфейсу до конкретного класу — є звужувальним (narrowing). Воно не відбувається автоматично, тому що не кожен IDiagnosable є Patient: інтерфейс можуть реалізовувати й інші класи. Для звужувального перетворення потрібне явне приведення або перевірка через is/as:

На практиці перевага — за is із pattern matching: він одночасно перевіряє тип і виконує приведення в одному виразі, не кидаючи винятків. Оператор as зручний, коли потрібно лише перевірити і не виконувати тіло if одразу. Явне приведення через (T) застосовується лише тоді, коли тип відомий з абсолютною впевненістю.

Перетворення типів через інтерфейс

Розроблено Tomka Yurii · © 2026 ·