Menu
Coddy logo textTech

Паттерн Адаптер

Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 57 из 70.

Паттерн Адаптер — это структурный паттерн, который позволяет несовместимым интерфейсам работать вместе. Он действует как мост между двумя классами, преобразуя интерфейс одного класса в интерфейс, который ожидают клиенты. Представьте его как адаптер питания, который позволяет подключить европейское устройство к американской розетке.

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

// Существующий класс с несовместимым интерфейсом
public class OldPrinter
{
    public void PrintMessage(string msg) => Console.WriteLine($"Old: {msg}");
}

// Интерфейс, который ожидает ваш код
public interface IPrinter
{
    void Print(string text);
}

// Адаптер устраняет разрыв
public class PrinterAdapter : IPrinter
{
    private OldPrinter _oldPrinter;
    
    public PrinterAdapter(OldPrinter oldPrinter)
    {
        _oldPrinter = oldPrinter;
    }
    
    public void Print(string text)
    {
        _oldPrinter.PrintMessage(text);  // Делегирует вызов старому интерфейсу
    }
}

Теперь ваш код может работать со старым принтером через ожидаемый интерфейс:

OldPrinter legacy = new OldPrinter();
IPrinter printer = new PrinterAdapter(legacy);

printer.Print("Hello");  // Старый: Hello

Паттерн Адаптер (Adapter) неоценим при интеграции сторонних библиотек или устаревшего кода в ваше приложение. Клиентский код работает с интерфейсом IPrinter, совершенно не подозревая о том, что за кулисами реальную работу выполняет старый, несовместимый класс.

challenge icon

Задание

Легко

Давайте создадим систему медиаплеера, используя паттерн Адаптер (Adapter). У вас есть существующая аудиобиблиотека, которая воспроизводит MP3-файлы, но вашему приложению требуется унифицированный интерфейс, способный обрабатывать различные типы медиа. Вы создадите адаптер, чтобы преодолеть этот разрыв, позволяя вашему современному медиаплееру беспрепятственно работать с устаревшей аудиобиблиотекой.

Вы организуете свой код в трех файлах:

  • LegacyAudio.cs: Представляет собой существующую библиотеку, которую вы не можете изменять. Создайте класс LegacyAudioPlayer в пространстве имен MediaSystem с методом PlayMp3(string filename), который возвращает строку "Playing MP3: {filename}". Это ваш «адаптируемый объект» (adaptee) — класс с несовместимым интерфейсом.
  • MediaAdapter.cs: Создайте интерфейс и адаптер в том же пространстве имен. Определите интерфейс IMediaPlayer с методом Play(string filename), который возвращает строку. Затем создайте класс MediaPlayerAdapter, реализующий IMediaPlayer. Ваш адаптер должен оборачивать экземпляр LegacyAudioPlayer (переданный через конструктор) и транслировать вызов Play в метод PlayMp3 устаревшего плеера.
  • Program.cs: Объедините все вместе, создав устаревший плеер, обернув его в адаптер и используя адаптер через интерфейс IMediaPlayer. Это демонстрирует, как клиентский код может работать с современным интерфейсом, в то время как адаптер берет на себя взаимодействие с устаревшей системой за кулисами.

Вы получите один входной параметр:

  • Имя файла для воспроизведения (например, song.mp3)

Создайте LegacyAudioPlayer, оберните его в MediaPlayerAdapter, затем вызовите Play у адаптера, используя интерфейс IMediaPlayer, и выведите результат.

Например, если на вход подается favorite_track.mp3, результат должен быть:

Playing MP3: favorite_track.mp3

Прелесть этого паттерна в том, что ваш основной код взаимодействует только с IMediaPlayer — он и понятия не имеет, что всю реальную работу выполняет устаревшая аудиобиблиотека. Если позже вам понадобится поддержка другой устаревшей библиотеки, вы просто создадите новый адаптер, не меняя клиентский код!

Шпаргалка

Паттерн Адаптер (Adapter) — это структурный паттерн, который позволяет несовместимым интерфейсам работать вместе, выступая в качестве моста между двумя классами.

Этот паттерн преобразует интерфейс одного класса в интерфейс, который ожидают клиенты. Он полезен, когда вы хотите использовать существующий класс, но его интерфейс не соответствует потребностям вашего кода.

Базовая структура:

// Существующий класс с несовместимым интерфейсом (Adaptee)
public class OldPrinter
{
    public void PrintMessage(string msg) => Console.WriteLine($"Old: {msg}");
}

// Целевой интерфейс, который ожидает ваш код
public interface IPrinter
{
    void Print(string text);
}

// Адаптер устраняет разрыв
public class PrinterAdapter : IPrinter
{
    private OldPrinter _oldPrinter;
    
    public PrinterAdapter(OldPrinter oldPrinter)
    {
        _oldPrinter = oldPrinter;
    }
    
    public void Print(string text)
    {
        _oldPrinter.PrintMessage(text);  // Делегирует выполнение старому интерфейсу
    }
}

Использование:

OldPrinter legacy = new OldPrinter();
IPrinter printer = new PrinterAdapter(legacy);

printer.Print("Hello");  // Old: Hello

Клиентский код работает с целевым интерфейсом, не подозревая о том, что за кулисами реальную работу выполняет несовместимый класс. Этот паттерн особенно ценен при интеграции сторонних библиотек или устаревшего кода.

Попробуйте сами

using System;
using MediaSystem;

class Program
{
    public static void Main(string[] args)
    {
        // Считать ввод имени файла
        string filename = Console.ReadLine();
        
        // TODO: Создать экземпляр LegacyAudioPlayer
        
        // TODO: Обернуть его в MediaPlayerAdapter
        
        // TODO: Использовать адаптер через интерфейс IMediaPlayer
        // и вывести результат вызова Play с именем файла
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование