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("otchet.txt"))
{
    Console.WriteLine("Пишем в файл...");
    // файл автоматически "закроется" после using
}

Вывод:

Открыт файл: otchet.txt
Пишем в файл...
Закрыт файл: otchet.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. Интерфейсы для тестирования (Mock-объекты)

В тестах очень важно изолировать код от внешних зависимостей: не лезть в настоящую базу, не трогать реальные файлы. Интерфейсы позволяют использовать заглушки (mock-объекты) и тестировать код, не затрагивая реальные базы или файлы.


public class FakeDataSaver : IDataSaver
{
    public bool WasCalled { get; private set; } = false;
    public void Save(string reportData)
    {
        WasCalled = true;
        Console.WriteLine("Сохранили данные в mock-объекте!");
    }
}

// В тесте
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;
}

Смысл: любой фреймворк, поддерживающий биндинг данных, ждёт, что ваш класс реализует этот интерфейс — и тогда UI будет автоматически обновляться при изменении свойств.

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();
        }
    }
}

Плагины могут разрабатываться третьими лицами — главное, чтобы они реализовывали интерфейс. Ваше приложение становится расширяемым!

2
Задача
C# SELF, 24 уровень, 4 лекция
Недоступна
Реализация интерфейса для геометрических фигур
Реализация интерфейса для геометрических фигур
2
Задача
C# SELF, 24 уровень, 4 лекция
Недоступна
Простая фабрика с использованием интерфейсов
Простая фабрика с использованием интерфейсов
1
Опрос
Использование интерфейсов, 24 уровень, 4 лекция
Недоступен
Использование интерфейсов
Продвинутые Интерфейсы
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Slevin Уровень 59
5 февраля 2026
Тест содержит кучу неточностей, двусмысленностей, вопросов о том, о чем мы понятия не можем иметь из лекций этого уровня. Мусор.
Ra Уровень 1 Student
2 декабря 2025
4 вопрос в задаче - приватные же тоже можно?