Шаблон проектирования — Адаптер (Adapter)
Название шаблона говорит само за себя. Он помогает адаптировать ваш код к новым требованиям, не существовавшим ранее без изменения исходных классов или интерфейсов. Допустим, что есть проект, веб-сайт, при помощи которого пользователи могут отправлять сообщения в твиттер компании. Например:
- Post.php — класс для отправки сообщений. Этот объект содержит в себе текст и URL сообщения.
- Twitter.php — Твиттер класс. Это самописный класс, или позаимствованный, например с packagist.org
Отлично. Вот пример кода, для отправки сообщения:
<?php
// создание сообщения и отправка в Twitter
$post = new Post();
$post->description = 'Моё первое сообщение Twitter!';
$post->url = 'http://devacademy.ru';
$post->send();
class Post
{
// ...
public function send()
{
$text = $this->description . ' ' . $this->url;
// любой Twitter класс
$twitter = new Twitter();
// аутентификация и т.д.
$twitter->tweet($text);
}
}
Здорово. Работает! Прекрасно справляется с поставленной задачей. Но, как обычно бывает, через некоторое время вам говорят, что теперь у пользователей должен быть выбор, между отправкой сообщений в Twitter или Instagram. Не проблема, легко!
<?php
// изменяем класс Post
class Post
{
public function send($service = 'twitter')
{
if ($service == 'twitter') {
$twitter = new Twitter();
// ...
$twitter->tweet();
} elseif ($service == 'instagram') {
$instagram = new Instagram();
// ...
$instagram->postToInstagram();
}
}
}
Вот она, ситуация для использования шаблона Адаптер (Adapter). У нас есть два разных класса, но с похожим функционалом, а для определения куда отправлять сообщение используется условная конструкция if
, а это плохо. От неё надо избавиться т.к. если добавится ещё один класс Facebook, то придётся переписывать метод send
.
В итоге нам нужно сделать, чтобы класс Post вообще не знал, в какой социальный сервис он отправляет сообщения. Он просто дожен создавать текст сообщения и вызывать определённые методы для их отправки. Проблема в том, что методы для отправки сообщений у классов Twitter и Instagram разные и изменять мы их не можем.
Для решения этой проблемы нужно создать дополнительные обёртки для этих классов. По сути эти обёртки и есть шаблон проектирования Адаптер, он просто преобразует разные интерфейсы одних классов к нужному интерфейсу для других классов (понятнее станет в примере ниже).
Создаем интерфейс службы
Итак, в нашем распоряжении две службы: Twitter и Instagram. Первая использует для отправки сообщений метод tweet(), а вторая — postToInstagram(). Для начала надо создать интерфейс сервиса, включающий в себя методы аутентификации и отправки сообщения в социальный сервис (неважно какой, т.к. у всех таких сервисов, есть авторизация и отправка сообщений). Вот пример такого интерфейса:
<?php
interface ServiceInterface
{
public function authenticate(array $options);
public function post($text, $url);
}
В этом интерфейсе присутствует два метода: один для аутентификации, а второй для отправки текста сообщения. Теперь создадим два класса (обратите внимание они реализуют наш интерфейс).
<?php
class TwitterService implements ServiceInterface
{
protected $service;
public function __construct()
{
$this->service = new Twitter();
}
public function authenticate(array $options)
{
$apiKey = $options['api_key'];
// ...
$this->service->authenticateUsingSomeMethod($apiKey);
// ...
}
public function post($text, $url)
{
// ...
$this->service->tweet($text . ' ' . $url);
// ...
}
}
<?php
class InstagramService implements ServiceInterface
{
protected $service;
public function __construct()
{
$this->service = new Instagram();
$this->service->someAnotherMethodYouHaveToCall();
}
public function authenticate(array $options)
{
// ...
$this->service->authenticateWithInstagramClass();
// ...
}
// Делегериуем работу по отправке сообщения, исходному классу
public function post($text, $url)
{
// ...
$this->service->postToInstagram($text);
// ...
}
}
Классы TwitterService и InstagramService являются адаптерами для исходных классов Twitter и Instagram. И хотя они по разному реализуют отправку сообщений, для нашего приложения это неважно, т.к. в нём используются классы адаптеры, которые заменяют старую логику на новую. Теперь, чтобы посмотреть как это работает, внесём изменения в класс Post.php.
<?php
class Post
{
protected $service;
// Здесь мы говорим, что для отправки нужно использовать адаптер
public function setServiceAdapter(ServiceInterface $service)
{
$this->service = $service;
}
public function send()
{
$this->service->post($this->description, $this->url);
}
}
Чтобы использовать класс Post, его сначала надо инициализировать, задав значение параметрам description
и url
, установить объект сервиса с типом ServiceInterface
и вызвать метод send()
:
<?php
$post = new Post();
// Если нужно отправить сообщение в Twitter
$post->setServiceAdapter(new TwitterService()); // ИЛИ
// Если нужно отправить сообщение в Instagram
$post->setServiceAdapter(new InstagramService()); // ИЛИ
// Если нужно отправить сообщение в Facebook
$post->setServiceAdapter(new FacebookService());
$post->description = 'Моё первое сообщение!';
$post->url = 'http://devacademy.ru';
$post->send();
Таким образом вы сможете создавать новые адаптеры без изменения кода класса Post. Этот класс использует интерфейс и только классы реализующие этот интерфейс будут иметь эти методы, а именно методы для отправки сообщений.
Исходный код шаблона доступен на github: https://github.com/padp/php_modern_design_patterns/tree/master/Adapter
Вот и все. Я надеюсь, что помог вам понять шаблон проектирования — Адаптер. Если у вас есть какие-то замечания и дополнения, я с удовольствием их выслушаю и внесу необходимые изменения в статью. Спасибо что дочитали до конца!