Menu
Coddy logo textTech

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

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

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

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

<?php
// Интерфейс, который ожидает ваше приложение
interface PaymentProcessor {
    public function pay(float $amount): string;
}

// Сторонняя библиотека с другим интерфейсом
class StripeAPI {
    public function createCharge(int $cents, string $currency): string {
        return "Charged {$cents} {$currency} via Stripe";
    }
}

Адаптер реализует ожидаемый вами интерфейс, используя внутри себя сторонний класс:

<?php
class StripeAdapter implements PaymentProcessor {
    public function __construct(private StripeAPI $stripe) {}
    
    public function pay(float $amount): string {
        $cents = (int)($amount * 100);
        return $this->stripe->createCharge($cents, 'USD');
    }
}

$stripe = new StripeAPI();
$processor = new StripeAdapter($stripe);
echo $processor->pay(29.99);

Вывод:

Charged 2999 USD via Stripe

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

challenge icon

Задание

Легко

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

Вы распределите свой код по четырем файлам:

  • TemperatureSensor.php — Определите интерфейс TemperatureSensor, который ожидает ваше погодное приложение. Он должен иметь метод getTemperature(): float, который возвращает температуру в градусах Цельсия.
  • LegacySensor.php — Создайте класс LegacySensor, который имитирует старое оборудование. Этот класс имеет интерфейс, отличный от того, который нужен вашему приложению:
    • Он принимает значение в Фаренгейтах в своем конструкторе (используйте продвижение свойств в конструкторе — constructor promotion)
    • Он имеет метод readFahrenheit(): float, который возвращает сохраненное значение в Фаренгейтах
  • LegacySensorAdapter.php — Подключите интерфейс TemperatureSensor и класс LegacySensor. Создайте класс LegacySensorAdapter, который реализует TemperatureSensor и оборачивает LegacySensor. Ваш адаптер должен:
    • Принимать LegacySensor в своем конструкторе
    • Реализовывать getTemperature(), считывая значение Фаренгейта из устаревшего датчика и конвертируя его в Цельсии по формуле: (fahrenheit - 32) * 5 / 9
  • main.php — Подключите файл LegacySensorAdapter. Вы получите на вход одно значение: температуру в Фаренгейтах.

    Создайте объект LegacySensor с входной температурой, затем оберните его в LegacySensorAdapter. Вызовите getTemperature() у адаптера и выведите результат, округленный до двух знаков после запятой.

    Формат вывода должен быть: Temperature: [celsius]C

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

Шпаргалка

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

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

Определите интерфейс, который ожидает ваше приложение:

<?php
interface PaymentProcessor {
    public function pay(float $amount): string;
}

Сторонний класс с другим интерфейсом:

<?php
class StripeAPI {
    public function createCharge(int $cents, string $currency): string {
        return "Charged {$cents} {$currency} via Stripe";
    }
}

Создание адаптера

Адаптер реализует ожидаемый интерфейс, используя внутри себя сторонний класс:

<?php
class StripeAdapter implements PaymentProcessor {
    public function __construct(private StripeAPI $stripe) {}
    
    public function pay(float $amount): string {
        $cents = (int)($amount * 100);
        return $this->stripe->createCharge($cents, 'USD');
    }
}

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

$stripe = new StripeAPI();
$processor = new StripeAdapter($stripe);
echo $processor->pay(29.99);
// Output: Charged 2999 USD via Stripe

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

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

<?php
require_once 'LegacySensorAdapter.php';

// Чтение входных данных
$fahrenheit = floatval(trim(fgets(STDIN)));

// TODO: Создать LegacySensor с входной температурой
// TODO: Обернуть его в LegacySensorAdapter
// TODO: Вызвать getTemperature() у адаптера
// TODO: Вывести результат, округленный до двух знаков после запятой
// Формат вывода: Temperature: [celsius]C

?>
quiz iconПроверьте себя

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

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