Уж очень напрашивается иллюстрация ниже. Наверное, вы уже догадались, в чем дело?
А теперь по порядку. Начнем с определения:
Адаптер — структурный шаблон проектирования, предназначенный для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс.
Рассмотрим определение в терминах вилка-розетка.
Розетка — это недоступный для модификации объект, к которому можно подключить устройства только с подходящей вилкой.
Для того, чтоб подключить устройство с отличающимся видом вилки, мы используем переходник (адаптер).
Кроме того, если мы захотим подключить устройство в другой вид розетки (тоже неподходящий), понадобится другой адаптер. Таким образом, адаптеров может быть разное количество, в зависимости от задачи.
А теперь вернемся к программированию. Я предлагаю выдуманный, но близкий к реальности пример.
Предположим, что разрабатывается сайт (интернет-магазин), поддерживающий разные виды оплаты:
- Бета-Банк
- ПриветБанк
- BayPal
Для нашего примера нужна возможность переводить деньги покупателя на счет владельца магазина во время совершения покупки.
Большинство платежных систем предоставляют SDK — класс или набор классов для работы с платежной системой. Представим, что для каждого из методов оплаты существует PHP класс, с нужным нам методом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/* * SDK классы предоставленные платежными системами */ class BetaBank { public function sendMoney($cardNumber, $amount) { // Реализация } // Другое содержимое } class PrivetBank { public function transfer($amount, $cardNumber) { // Реализация } // Другое содержимое } class BayPal { public function sendTo($cardNumber, $amount) { // Реализация } // Другое содержимое } |
После того, как покупатель выбрал метод, мы создаем объект метода оплаты (одного из классов выше):
1 2 3 4 5 6 |
/* * Создаем объект метода оплаты */ $paymentMethod = new BetaBank(); // или: $paymentMethod = new PrivetBank(); // или: $paymentMethod = new BayPall(); |
Здесь очень уместным будет еще один шаблон — стратегия. Можете прочесть как и зачем его использовать позже.
По задумке осуществить оплату было бы очень удобно вот так:
1 2 3 4 |
/* * Осуществление оплаты */ $paymentMethod->pay($card, $amount); |
Но… Есть проблема. Так как SDK классы были «разработаны разными людьми», в разное время, без каких-либо договоренностей между собой, то названия методов и порядок аргументов в них отличаются. Хотя они делают одно и те же действие — переводят X денег на карту N.
Что делать?
Правильно! Применить «переходник» — адаптер. Такой же, какой вы применяете для розетки, только для класса. Решение может выглядеть вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/* * Пример использования шаблона Адаптер */ interface PaymentAdapterInterface { public function pay($cardNumber, $amount); } class BetaBankAdapter implements PaymentAdapterInterface { public $method; public function __construct() { $this->method = new BetaBank(); } public function pay($cardNumber, $amount) { $this->method->sendMoney($cardNumber, $amount); } } class PrivetBankAdapter implements PaymentAdapterInterface { public $method; public function __construct() { $this->method = new PrivetBank(); } public function pay($cardNumber, $amount) { $this->method->transfer($amount, $cardNumber); } } // Другие адаптеры |
В коде выше я создал два адаптера для классов BetaBank и PrivetBank. Методы pay() в каждом из них вызывают соответствующие методы SDK классов с правильными именами и порядком аргументов.
Интерфейс PaymentAdapterInterface используется для того, чтоб гарантировать существование метода pay() в конкретной реализации класса-адаптера.
Теперь в клиентском коде осуществлять оплату можно вот так:
1 2 3 4 5 6 7 8 9 10 |
/* * Клиентский код */ $paymentMethod = new BetaBankAdapter(); // Или: $paymentMethod = new PrivetBankAdapter(); // ... $paymentMethod->pay($cardNumber, $amount); |
Вместо выводов
Паттерн адаптер обеспечивает возможность работы с объектами, у которых разные интерфейсы. При этом пропадает необходимость переписывать существующий код к требуемому интерфейсу.
Благодарен, как всегда доступно )