PHP: Адаптер

Adapter Адаптер Adapter

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

Адаптер выступает прослойкой между двумя объектами, превращая вызовы одного в вызовы понятные другому.

Подробней об Адаптере

Особенности паттерна в PHP

Сложность:

Популярность:

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

Пример: Структура паттерна

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

AdapterStructural.php: Пример структуры паттерна

<?php

namespace RefactoringGuru\Adapter\Structural;

/**
 * Целевой класс объявляет интерфейс, с которым может работать клиентский код.
 */
class Target
{
    public function request()
    {
        return "Target: The default target's behavior.";
    }
}

/**
 * Адаптируемый класс содержит некоторое полезное поведение, но его интерфейс
 * несовместим  с существующим клиентским кодом. Адаптируемый класс нуждается в
 * некоторой доработке,  прежде чем клиентский код сможет его использовать.
 */
class Adaptee
{
    public function specificRequest()
    {
        return ".eetpadA eht fo roivaheb laicepS";
    }
}

/**
 * Адаптер делает интерфейс Адаптируемого класса совместимым с целевым
 * интерфейсом.
 */
class Adapter extends Target
{
    private $adaptee;

    public function __construct(Adaptee $adaptee)
    {
        $this->adaptee = $adaptee;
    }

    public function request()
    {
        return "Adapter: (TRANSLATED) ".strrev($this->adaptee->specificRequest());
    }
}

/**
 * Клиентский код поддерживает все классы, использующие целевой интерфейс.
 */
function clientCode(Target $target)
{
    print($target->request());
}

print("Client: I can work just fine with the Target objects:\n");
$target = new Target();
clientCode($target);
print("\n\n");

$adaptee = new Adaptee();
print("Client: The Adaptee class has a weird interface. See, I don't understand it:\n");
print($adaptee->specificRequest());
print("\n\n");

print("Client: But I can work with it via the Adapter:\n");
$adapter = new Adapter($adaptee);
clientCode($adapter);

Output.txt: Результат выполнения

Client: I can work just fine with the Target objects:
Target: The default target's behavior.

Client: The Adaptee class has a weird interface. See, I don't understand it:
.eetpadA eht fo roivaheb laicepS

Client: But I can work with it via the Adapter:
Adapter: (TRANSLATED) Special behavior of the Adaptee.

Пример: Пример из жизни

Паттерн Адаптер позволяет использовать сторонние или устаревшие классы, даже если они несовместимы с основной частью кода. Например, вместо того, чтобы переписывать интерфейс уведомлений вашего приложения для поддержки каждого стороннего сервиса вроде Slack, Facebook, SMS и прочих, вы создаёте под эти сервисы набор специальных обёрток, которые приводят вызовы из приложения к требуемым сторонними классами интерфейсу и формату.

AdapterRealWorld.php: Пример из жизни

<?php

namespace RefactoringGuru\Adapter\RealWorld;

/**
 * Паттерн Адаптер
 *
 * Назначение: Преобразует интерфейс класса в интерфейс, ожидаемый клиентами.
 * Адаптер позволяет классам с несовместимыми интерфейсами работать вместе.
 *
 * Пример: Паттерн Адаптер позволяет использовать сторонние или устаревшие
 * классы,  даже если они несовместимы с основной частью кода. Например, вместо
 * того, чтобы  переписывать интерфейс уведомлений вашего приложения для
 * поддержки каждого стороннего сервиса вроде Slack, Facebook, SMS и прочих, вы
 * создаёте под эти сервисы набор специальных обёрток,  которые приводят вызовы
 * из приложения к требуемым сторонними классами интерфейсу и формату.
 */

/**
 * Целевой интерфейс предоставляет интерфейс, которому следуют классы вашего
 * приложения.
 */
interface Notification
{
    public function send(string $title, string $message);
}

/**
 * Вот пример существующего класса, который следует за целевым интерфейсом.
 *
 * Дело в том, что у большинства приложений нет чётко определённого интерфейса.
 * В этом случае лучше было бы расширить Адаптер за счёт существующего класса
 * приложения.  Если это неудобно (например, SlackNotification не похож на
 * подкласс EmailNotification),  тогда первым шагом должно быть извлечение
 * интерфейса.
 */
class EmailNotification implements Notification
{
    private $adminEmail;

    public function __construct(string $adminEmail)
    {
        $this->adminEmail = $adminEmail;
    }

    public function send(string $title, string $message)
    {
        mail($this->adminEmail, $title, $message);
        print("Sent email with title '$title' to '{$this->adminEmail}' that says '$message'.");
    }
}

/**
 * Адаптируемый класс – некий полезный класс, несовместимый с целевым
 * интерфейсом.  Нельзя просто войти и изменить код класса так, чтобы следовать
 * целевому интерфейсу,  так как код может предоставляться сторонней
 * библиотекой.
 */
class SlackApi
{
    private $login;
    private $apiKey;

    public function __construct(string $login, string $apiKey)
    {
        $this->login = $login;
        $this->apiKey = $apiKey;
    }

    public function logIn()
    {
        // Send authentication request to Slack web service.
        print("Logged in to a slack account '{$this->login}'.\n");
    }

    public function sendMessage($chatId, $message)
    {
        // Send message post request to Slack web service.
        print("Posted following message into the '$chatId' chat: '$message'.\n");
    }
}

/**
 * Адаптер – класс, который связывает Целевой интерфейс и Адаптируемый класс.
 * Это позволяет приложению использовать Slack API для отправки уведомлений.
 */
class SlackNotification implements Notification
{
    private $slack;
    private $chatId;

    public function __construct(SlackApi $slack, string $chatId)
    {
        $this->slack = $slack;
        $this->chatId = $chatId;
    }

    /**
     * Адаптер способен адаптировать интерфейсы и преобразовывать входные данные
     * в формат,  необходимый Адаптируемому классу.
     */
    public function send(string $title, string $message)
    {
        $slackMessage = "#" . $title . "# " . strip_tags($message);
        $this->slack->logIn();
        $this->slack->sendMessage($this->chatId, $slackMessage);
    }
}

/**
 * Клиентский код работает с классами, которые следуют Целевому интерфейсу.
 */
function clientCode(Notification $notification)
{
    // ...

    print($notification->send("Website is down!",
        "<strong style='color:red;font-size: 50px;'>Alert!</strong> " .
        "Our website is not responding. Call admins and bring it up!"));

    // ...
}

print("Client code is designed correctly and works with email notifications:\n");
$notification = new EmailNotification("developers@example.com");
clientCode($notification);
print("\n\n");


print("The same client code can work with other classes via adapter:\n");
$slackApi = new SlackApi("example.com", "XXXXXXXX");
$notification = new SlackNotification($slackApi, "Example.com Developers");
clientCode($notification);

Output.txt: Результат выполнения

Client code is designed correctly and works with email notifications:
Sent email with title 'Website is down!' to 'developers@example.com' that says '<strong style='color:red;font-size: 50px;'>Alert!</strong> Our website is not responding. Call admins and bring it up!'.

The same client code can work with other classes via adapter:
Logged in to a slack account 'example.com'.
Posted following message into the 'Example.com Developers' chat: '#Website is down!# Alert! Our website is not responding. Call admins and bring it up!'.