Adapter
Проблем
Да се разработи част от приложение за обработка на плащания.
Приложението вече работи с банков платежен процесор. По-късно трябва да бъде добавена възможност за плащане чрез външна услуга PayPal.
Решение без използване на шаблона
public class Application {
public static void main(String[] args) {
BankPaymentProcessor bankProcessor =
new BankPaymentProcessor();
bankProcessor.pay(120.50);
ExternalPayPalService payPalService =
new ExternalPayPalService();
payPalService.makePayment(120.50);
}
}
Недостатъци на решението
При този подход клиентският код трябва да познава различните начини за извършване на плащане. Банковият процесор използва метода pay(), а външната PayPal услуга — метода makePayment().
Това води до зависимост от конкретните класове и затруднява добавянето на нови платежни системи, защото клиентският код трябва да бъде променян за всяка нова външна услуга.
Следователно е необходимо решение, което позволява всички платежни системи да бъдат използвани чрез един и същ интерфейс.
Шаблонът като решение
Шаблонът Adapter позволява клас с различен интерфейс да бъде използван чрез интерфейс, който клиентският код вече познава.
Адаптерът обвива външния клас и преобразува извикванията от очаквания интерфейс към реалните методи на адаптирания клас.
Дефиниция
Adapter е структурен шаблон за проектиране, който позволява обекти с несъвместими интерфейси да работят заедно, без да се променя техният съществуващ код.
В описанието на шаблона Adapter често се използват следните термини:
- Target – интерфейсът, който клиентският код очаква да използва.
- Adaptee – съществуващият клас с несъвместим интерфейс, който трябва да бъде използван.
- Adapter – класът, който реализира интерфейса Target и преобразува извикванията към Adaptee.
Реализации на шаблона Adapter
Съществуват два основни начина за реализиране на шаблона Adapter.
Class Adapter
- използва наследяване;
- адаптерът наследява адаптирания клас и имплементира целевия интерфейс;
- поради липсата на множествено наследяване на класове в Java този подход се използва сравнително рядко.
Object Adapter
- използва композиция (делегиране чрез съдържане на референция към адаптирания обект);
- адаптерът съдържа референция към адаптирания обект;
- осигурява по-слаба свързаност между класовете;
- това е най-често използваната реализация в Java.
В настоящото упражнение ще бъде разгледан Object Adapter.
| Class Adapter | Object Adapter |
|---|---|
| използва наследяване | използва композиция |
| по-силна свързаност | по-слаба свързаност |
| по-труден за разширяване | по-гъвкав |
| използва се рядко в Java | предпочитан подход |
UML диаграма
| Роля | Пример |
|---|---|
| Target | PaymentProcessor |
| Adapter | PayPalAdapter |
| Adaptee | ExternalPayPalService |
| Client | Application |
Примерна реализация
public interface PaymentProcessor {
boolean pay(double amount);
}
public class BankPaymentProcessor implements PaymentProcessor {
@Override
public boolean pay(double amount) {
return true;
}
}
public class ExternalPayPalService {
public boolean makePayment(double amount) {
return true;
}
}
public class PayPalAdapter implements PaymentProcessor {
private ExternalPayPalService payPalService;
public PayPalAdapter(ExternalPayPalService payPalService) {
this.payPalService = payPalService;
}
@Override
public boolean pay(double amount) {
return payPalService.makePayment(amount);
}
}
public class Application {
public static void main(String[] args) {
PaymentProcessor bankProcessor = new BankPaymentProcessor();
if (bankProcessor.pay(120.50)) {
System.out.println("Payment completed successfully.");
}
ExternalPayPalService payPalService = new ExternalPayPalService();
PaymentProcessor payPalProcessor = new PayPalAdapter(payPalService);
if (payPalProcessor.pay(120.50)) {
System.out.println("Payment completed successfully.");
}
}
}
В примера приложението работи единствено с интерфейса PaymentProcessor. То не знае дали плащането ще бъде извършено чрез стандартната банкова реализация или чрез външната услуга PayPal.
Класът ExternalPayPalService представлява вече съществуваща външна библиотека, чийто интерфейс (makePayment()) не съответства на интерфейса, използван от приложението (pay()).
Класът PayPalAdapter реализира интерфейса PaymentProcessor и съдържа референция към обект от тип ExternalPayPalService. При извикване на метода pay() адаптерът делегира изпълнението към метода makePayment() на външната библиотека.
По този начин останалата част от приложението работи единствено с интерфейса PaymentProcessor и не се налагат промени в клиентския код при интегриране на нова платежна система.
Ако в бъдеще се наложи използването на друга външна платежна система (например Stripe), ще бъде достатъчно създаването на нов адаптер, без промяна в съществуващия клиентски код.
[!IMPORTANT]
В литературата за шаблоните за проектиране често се среща изразът “използва композиция вместо наследяване” (favor composition over inheritance).
В този контекст терминът композиция не се използва в тесния UML смисъл на отношение част–цяло, при което един обект управлява жизнения цикъл на друг.
Вместо това той означава, че даден клас съдържа референция към друг обект и делегира част от своето поведение към него, вместо да наследява неговата функционалност.
Именно този подход осигурява по-слаба свързаност между класовете и по-лесно разширяване на системата. Поради тази причина реализацията чрез Object Adapter е предпочитаният подход в Java.
Предимства
- позволява използване на съществуващи класове с несъвместим интерфейс;
- намалява необходимостта от промяна в клиентския код;
- отделя логиката за преобразуване в самостоятелен клас;
- улеснява интеграцията с външни библиотеки и услуги.
Недостатъци
- добавя допълнителен клас в структурата;
- при много адаптери системата може да стане по-трудна за проследяване;
- не решава проблеми в логиката на адаптирания клас, а само променя начина на достъп до него.
Приложение
Adapter се използва когато:
- съществуващ клас трябва да бъде използван чрез друг интерфейс;
- се интегрира външна библиотека или услуга;
- клиентският код не трябва да бъде променян;
- е необходимо стар код да бъде включен в нова система.