JavaRush /Курси /C# SELF /Використання інтерфейсів на практиці

Використання інтерфейсів на практиці

C# SELF
Рівень 24 , Лекція 4
Відкрита

1. Інтерфейси в бізнес-логіці: кнопки та дії

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

Інтерфейси особливо цінні у великих застосунках. Тому нижче наведено складніші випадки, іноді з випередженням програми курсу. Якщо цікаво — чудово! Якщо ні — перейдіть на наступний рівень :P

Найпростіший приклад — кнопки та клацання на них

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

Ми хочемо, щоб одні елементи реагували на натискання (кнопки), а інші — ні (наприклад, статичний заголовок). Інтерфейси дозволяють елегантно реалізувати такі сценарії.

Крок 1: Створюємо інтерфейс


// Компонент, який можна клацнути:
public interface IClickable
{
    void Click();
}

Крок 2: Застосовуємо інтерфейс для різних типів елементів


// Базовий клас для всіх елементів меню
public class MenuItem
{
    public string Title { get; set; }

    public MenuItem(string title)
    {
        Title = title;
    }

    public virtual void Display()
    {
        Console.WriteLine(Title);
    }
}

// Кнопка: її можна клацнути
public class Button : MenuItem, IClickable
{
    public Button(string title) : base(title) {}

    public void Click()
    {
        Console.WriteLine($"[Кнопка] {Title} була натиснута!");
    }
}

// Просто напис: клацати не можна
public class Label : MenuItem
{
    public Label(string title) : base(title) {}
}

Крок 3: Використовуємо поліморфізм інтерфейсу


IClickable[] clickableItems = new IClickable[]
{
    new Button("Зберегти"),
    new Button("Вийти")
    // Label тут додати не можна — вона не IClickable!
};

foreach (var item in clickableItems)
{
    item.Click();
}

Бачите «магію»? Список приймає лише тих, хто «вміє» Click, тож ви не отримаєте помилки під час виконання, якщо спробуєте клацнути на невідповідному обʼєкті.

2. Інтерфейси та патерн «Стратегія»: вибір алгоритму на льоту

Уявіть: ви пишете застосунок, у якому можна зберігати звіти різними способами: у файл, у базу даних, у «хмару», а може, і в Telegram вашому керівникові (не питайте, всяке буває).

Крок 1: Формулюємо завдання

Потрібно, щоб клас ReportGenerator міг працювати з будь-яким способом збереження, не знаючи деталей реалізації.

Крок 2: Описуємо інтерфейс-стратегію


public interface IDataSaver
{
    void Save(string reportData);
}

Крок 3: Реалізуємо різні варіанти


public class FileDataSaver : IDataSaver
{
    public void Save(string reportData)
    {
        Console.WriteLine("[FileDataSaver] Зберігаю у файл...\n" + reportData);
        // Тут міг би бути код для File.WriteAllText(...)
    }
}

public class DatabaseDataSaver : IDataSaver
{
    public void Save(string reportData)
    {
        Console.WriteLine("[DatabaseDataSaver] Зберігаю у базу даних...\n" + reportData);
        // Тут міг би бути код для запису у базу даних
    }
}

Крок 4: Використовуємо інтерфейс для підміни стратегії збереження


public class ReportGenerator
{
    private readonly IDataSaver _dataSaver;

    public ReportGenerator(IDataSaver dataSaver)
    {
        _dataSaver = dataSaver;
    }

    public void GenerateReport()
    {
        string report = "Це важливий звіт!";
        Console.WriteLine("Генеруємо звіт...");
        _dataSaver.Save(report);
    }
}

Демонстрація гнучкості


// Можна підмінити спосіб збереження без переписування ReportGenerator!
IDataSaver fileSaver = new FileDataSaver();
ReportGenerator fileReport = new ReportGenerator(fileSaver);
fileReport.GenerateReport();

IDataSaver dbSaver = new DatabaseDataSaver();
ReportGenerator dbReport = new ReportGenerator(dbSaver);
dbReport.GenerateReport();

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

3. Інтерфейси в .NET: IDisposable і using

Один із найпоширеніших інтерфейсів у .NET — IDisposable. Його реалізують усі класи, які працюють з некерованими ресурсами: файлами, потоками, мережевими з’єднаннями.

Навіщо потрібен IDisposable?

Коли ви працюєте з ресурсом, який обов’язково треба звільнити (наприклад, закрити файл), ви реалізуєте IDisposable і пишете метод Dispose. Це дозволяє передавати такі об’єкти в оператор using, який гарантує виклик Dispose під час виходу з блоку.

Приклад: симуляція роботи з файлом

public class FakeFile : IDisposable
{
    public string FileName { get; }

    public FakeFile(string fileName)
    {
        FileName = fileName;
        Console.WriteLine($"Відкрито файл: {fileName}");
    }

    public void Dispose()
    {
        Console.WriteLine($"Закрито файл: {FileName}");
    }
}

// В основній програмі:
using (var file = new FakeFile("zvit.txt"))
{
    Console.WriteLine("Пишемо у файл...");
    // файл автоматично "закриється" після using
}

Виведення:

Відкрито файл: zvit.txt
Пишемо у файл...
Закрито файл: zvit.txt

Справді корисно: ви не забудете закрити файл — інтерфейс і інфраструктура мови вас захистять.

4. Інтерфейси в колекціях та LINQ

Коли ви працюєте зі списками, масивами, словниками — ви вже використовуєте інтерфейси, навіть якщо про це не замислюєтеся.


List<int> list = new List<int> { 1, 2, 3 };
IEnumerable<int> enumerable = list; // усе гаразд!

// Тепер ви можете перебрати елементи так:
foreach(var x in enumerable)
{
    Console.WriteLine(x);
}

Більшість методів LINQ працюють з колекціями через інтерфейс IEnumerable<T>. Це дозволяє писати код, який не залежить від конкретного типу колекції.

Навіщо це потрібно?
Ви можете замінити List<T> на T[], HashSet<T> або навіть власну колекцію — і ваш код працюватиме далі!

5. Інтерфейси для тестування (мок-обʼєкти)

У тестах дуже важливо ізолювати код від зовнішніх залежностей: не звертатися до справжньої бази, не чіпати реальні файли. Інтерфейси дозволяють використовувати заглушки (мок-обʼєкти) й тестувати код, не торкаючись реальних баз чи файлів.


public class FakeDataSaver : IDataSaver
{
    public bool WasCalled { get; private set; } = false;
    public void Save(string reportData)
    {
        WasCalled = true;
        Console.WriteLine("Зберегли дані у мок-обʼєкті!");
    }
}

// У тесті
FakeDataSaver saver = new FakeDataSaver();
ReportGenerator generator = new ReportGenerator(saver);
generator.GenerateReport();

Console.WriteLine($"Чи було викликано Save? {saver.WasCalled}");

Результат: тест не залежить від реального світу, але підтверджує, що потрібний метод було викликано!

6. Явна реалізація інтерфейсів для розвʼязання конфліктів

Іноді клас має реалізувати два інтерфейси з однаковими методами, але логіка цих методів різниться.


public interface IFlyable
{
    void Move();
}

public interface ISwimmable
{
    void Move();
}

public class Duck : IFlyable, ISwimmable
{
    // Явна реалізація
    void IFlyable.Move()
    {
        Console.WriteLine("Качка летить!");
    }

    void ISwimmable.Move()
    {
        Console.WriteLine("Качка пливе!");
    }
}

Duck duck = new Duck();

// duck.Move(); // Помилка: немає такого методу!
((IFlyable)duck).Move(); // "Качка летить!"
((ISwimmable)duck).Move(); // "Качка пливе!"

Це може здаватися суворим, але іноді саме цього вимагають специфікації!

7. Інтерфейси в подієвій моделі: INotifyPropertyChanged

У .NET є стандартні інтерфейси для підтримки подій — наприклад, коли в моделях даних щось змінюється, і UI має про це дізнатися (дуже часто у WPF, MAUI та інших GUI).


using System.ComponentModel;

public class Person : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get => name;
        set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
            }
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;
}

Сенс: будь-який фреймворк, що підтримує data binding, очікує, що ваш клас реалізує цей інтерфейс — і тоді інтерфейс користувача автоматично оновлюватиметься під час зміни властивостей.

8. Інтерфейси в патерні «Фабрика» (Factory)

Інтерфейси ідеально підходять для побудови фабрик — класів, які створюють різні, але взаємозамінні обʼєкти.


public interface ITransport
{
    void Move();
}

public class Bicycle : ITransport
{
    public void Move() => Console.WriteLine("Їдемо на велосипеді!");
}

public class Car : ITransport
{
    public void Move() => Console.WriteLine("Їдемо на машині!");
}

public class TransportFactory
{
    public static ITransport Create(string type)
    {
        return type switch
        {
            "bike" => new Bicycle(),
            "car" => new Car(),
            _ => throw new ArgumentException("Невідомий тип транспорту")
        };
    }
}

ITransport transport = TransportFactory.Create("bike");
transport.Move(); // "Їдемо на велосипеді!"

9. Інтерфейси для подій: приклад власної події

Опишемо інтерфейс «слухача події»:


public interface ILoginListener
{
    void OnLogin(string userName);
}

// Клас, який викликає подію
public class LoginManager
{
    private List<ILoginListener> listeners = new();

    public void Subscribe(ILoginListener listener) => listeners.Add(listener);

    public void Login(string userName)
    {
        Console.WriteLine($"Користувач {userName} увійшов.");
        foreach (var listener in listeners)
            listener.OnLogin(userName);
    }
}

// Клас-реалізатор інтерфейсу
public class WelcomeMessage : ILoginListener
{
    public void OnLogin(string userName)
    {
        Console.WriteLine($"Вітаємо, {userName}!");
    }
}

LoginManager manager = new();
manager.Subscribe(new WelcomeMessage());
manager.Login("Вася");
// Користувач Вася увійшов.
// Вітаємо, Вася!

На співбесіді: якщо запитають — «як би ви реалізували власну подієву систему?», сміливо розповідайте про інтерфейси-слухачі!

10. Інтерфейси для плагінів (розширюваність застосунків)

Багато великих застосунків підтримують плагіни. Завдяки інтерфейсам ваш застосунок може «завантажувати» нові модулі на льоту, не знаючи їхньої внутрішньої кухні.


public interface IPlugin
{
    string Name { get; }
    void Run();
}

// Твій застосунок:
public class PluginLoader
{
    public void LoadAndRun(IEnumerable<IPlugin> plugins)
    {
        foreach (var plugin in plugins)
        {
            Console.WriteLine($"Запускаю плагін: {plugin.Name}");
            plugin.Run();
        }
    }
}

Плагіни можуть розробляти треті особи — головне, щоб вони реалізували інтерфейс. Ваш застосунок стає розширюваним!

1
Опитування
Використання інтерфейсів, рівень 24, лекція 4
Недоступний
Використання інтерфейсів
Просунуті Інтерфейси
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ