Статьи

Пример шаблона дизайна адаптера

Эта статья является частью нашего курса Академии под названием « Шаблоны проектирования Java» .

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

1. Шаблон адаптера

Макс, разработчик программного обеспечения, работал над сайтом электронной коммерции. Сайт позволяет пользователям делать покупки и оплачивать покупки онлайн. Сайт интегрирован со сторонним платежным шлюзом, через который пользователи могут оплачивать свои счета с помощью своей кредитной карты. Все шло хорошо, пока его менеджер не вызвал его для изменения проекта.

Менеджер сказал ему, что они планируют сменить поставщика платежного шлюза, и он должен реализовать это в коде.

Проблема, которая возникает здесь, заключается в том, что сайт подключен к платежному шлюзу Xpay, который принимает объект типа Xpay. Новый поставщик, PayD, разрешает процесс только объектам типа PayD. Макс не хочет менять весь набор из 100 классов, которые имеют ссылку на объект типа XPay. Это также повышает риск для проекта, который уже запущен в производство. Также он не может изменить сторонний инструмент платежного шлюза. Проблема возникла из-за несовместимых интерфейсов между двумя различными частями кода. Чтобы запустить процесс, Макс должен найти способ сделать код совместимым с предоставленным поставщиком API.

Текущий код с API Xpay

фигура 1

фигура 1

Теперь текущий интерфейс кода не совместим с интерфейсом нового поставщика.

фигура 2

фигура 2

2. Адаптер для спасения

Здесь Максу нужен адаптер, который может находиться между кодом и API вендора и позволять процессу протекать. Но перед решением давайте сначала посмотрим, что такое адаптер и как он работает.

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

Шаблон Adapter позволяет адаптировать то, что объект или класс предоставляет, к тому, что ожидает другой объект или класс. Он преобразует интерфейс класса в другой интерфейс, который ожидает клиент. Это позволяет классам работать вместе, что иначе невозможно из-за несовместимых интерфейсов. Это позволяет исправить интерфейс между объектами и классами без непосредственного изменения объектов и классов.

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

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

Рисунок 3

Рисунок 3

Теперь давайте посмотрим, как это решит проблему Макса.

3. Решение проблемы

В настоящее время код доступен интерфейсу Xpay. Интерфейс выглядит примерно так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package com.javacodegeeks.patterns.adapterpattern.xpay;
 
public interface Xpay {
     
    public String getCreditCardNo();
    public String getCustomerName();
    public String getCardExpMonth();
    public String getCardExpYear();
    public Short getCardCVVNo();
    public Double getAmount();
     
    public void setCreditCardNo(String creditCardNo);
    public void setCustomerName(String customerName);
    public void setCardExpMonth(String cardExpMonth);
    public void setCardExpYear(String cardExpYear);
    public void setCardCVVNo(Short cardCVVNo);
    public void setAmount(Double amount);
     
}

Он содержит набор методов установки и метода получения, используемых для получения информации о кредитной карте и имени клиента. Этот интерфейс Xpay реализован в коде, который используется для создания экземпляра объекта этого типа, и предоставляет объект API поставщика.

Следующий класс определяет реализацию интерфейса Xpay .

01
02
03
04
05
06
07
08
09
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.javacodegeeks.patterns.adapterpattern.site;
 
import com.javacodegeeks.patterns.adapterpattern.xpay.Xpay;
 
public class XpayImpl implements Xpay{
 
    private String creditCardNo;
    private String customerName;
    private String cardExpMonth;
    private String cardExpYear;
    private Short cardCVVNo;
    private Double amount;
     
    @Override
    public String getCreditCardNo() {
        return creditCardNo;
    }
 
    @Override
    public String getCustomerName() {
        return customerName;
    }
 
    @Override
    public String getCardExpMonth() {
        return cardExpMonth;
    }
 
    @Override
    public String getCardExpYear() {
        return cardExpYear;
    }
 
    @Override
    public Short getCardCVVNo() {
        return cardCVVNo;
    }
 
    @Override
    public Double getAmount() {
        return amount;
    }
 
    @Override
    public void setCreditCardNo(String creditCardNo) {
        this.creditCardNo = creditCardNo;
    }
 
    @Override
    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }
 
    @Override
    public void setCardExpMonth(String cardExpMonth) {
        this.cardExpMonth = cardExpMonth;
    }
 
    @Override
    public void setCardExpYear(String cardExpYear) {
        this.cardExpYear = cardExpYear;
    }
 
    @Override
    public void setCardCVVNo(Short cardCVVNo) {
        this.cardCVVNo = cardCVVNo;
    }
 
    @Override
    public void setAmount(Double amount) {
        this.amount = amount;
    }
 
}

Ключевой интерфейс нового вендора выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package com.javacodegeeks.patterns.adapterpattern.payd;
 
public interface PayD {
     
    public String getCustCardNo();
    public String getCardOwnerName();
    public String getCardExpMonthDate();
    public Integer getCVVNo();
    public Double getTotalAmount();
     
    public void setCustCardNo(String custCardNo);
    public void setCardOwnerName(String cardOwnerName);
    public void setCardExpMonthDate(String cardExpMonthDate);
    public void setCVVNo(Integer cVVNo);
    public void setTotalAmount(Double totalAmount);
}

Как видите, этот интерфейс имеет набор различных методов, которые необходимо реализовать в коде. Но Xpay создается большей частью кода, очень сложно и рискованно менять весь набор классов.

Нам нужен какой-то способ, способный выполнить требование поставщика, чтобы обработать платеж, а также внести меньше или не вносить изменений в текущий код. Способ обеспечивается шаблоном Адаптера.

Мы создадим адаптер, который будет иметь тип PayD , и он Xpay объект Xpay (тип, который он предполагает адаптировать).

01
02
03
04
05
06
07
08
09
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.javacodegeeks.patterns.adapterpattern.site;
 
import com.javacodegeeks.patterns.adapterpattern.payd.PayD;
import com.javacodegeeks.patterns.adapterpattern.xpay.Xpay;
 
public class XpayToPayDAdapter implements PayD{
 
    private String custCardNo;
    private String cardOwnerName;
    private String cardExpMonthDate;
    private Integer cVVNo;
    private Double totalAmount;
     
    private final Xpay xpay;
     
    public XpayToPayDAdapter(Xpay xpay){
        this.xpay = xpay;
        setProp();
    }
 
    @Override
    public String getCustCardNo() {
        return custCardNo;
    }
 
    @Override
    public String getCardOwnerName() {
        return cardOwnerName;
    }
 
    @Override
    public String getCardExpMonthDate() {
        return cardExpMonthDate;
    }
 
    @Override
    public Integer getCVVNo() {
        return cVVNo;
    }
 
    @Override
    public Double getTotalAmount() {
        return totalAmount;
    }
 
    @Override
    public void setCustCardNo(String custCardNo) {
        this.custCardNo = custCardNo;
    }
 
    @Override
    public void setCardOwnerName(String cardOwnerName) {
        this.cardOwnerName = cardOwnerName;
    }
 
    @Override
    public void setCardExpMonthDate(String cardExpMonthDate) {
        this.cardExpMonthDate = cardExpMonthDate;
    }
 
    @Override
    public void setCVVNo(Integer cVVNo) {
        this.cVVNo = cVVNo;
    }
 
    @Override
    public void setTotalAmount(Double totalAmount) {
        this.totalAmount = totalAmount;
    }
     
    private void setProp(){
        setCardOwnerName(this.xpay.getCustomerName());
        setCustCardNo(this.xpay.getCreditCardNo());
        setCardExpMonthDate(this.xpay.getCardExpMonth()+"/"+this.xpay.getCardExpYear());
        setCVVNo(this.xpay.getCardCVVNo().intValue());
        setTotalAmount(this.xpay.getAmount());
    }
 
}

В приведенном выше коде мы создали адаптер ( XpayToPayDAdapter ). Адаптер реализует интерфейс PayD, так как он должен имитировать PayD типа PayD . Адаптер использует композицию объекта для хранения объекта, который он должен адаптировать, типа объекта Xpay . Объект передается в адаптер через его конструктор.

Теперь, пожалуйста, обратите внимание, что у нас есть два несовместимых типа интерфейсов, которые нам нужно объединить с помощью адаптера, чтобы код работал. Эти два интерфейса имеют различный набор методов. Но единственная цель этих интерфейсов очень похожа, т.е. предоставлять информацию о клиенте и кредитной карте их конкретным поставщикам.

Метод setProp() вышеприведенного класса используется для установки свойств xpay в объект payD. Мы устанавливаем методы, которые похожи в работе в обоих интерфейсах. Однако в интерфейсе PayD есть только один метод для установки месяца и года кредитной карты, в отличие от двух методов в интерфейсе Xpay . Мы объединили результат двух методов объекта Xpay ( this.xpay.getCardExpMonth()+"/"+this.xpay.getCardExpYear() ) и установили его в метод setCardExpMonthDate() .

Давайте проверим приведенный выше код и посмотрим, сможет ли он решить проблему Макса.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.javacodegeeks.patterns.adapterpattern.site;
 
import com.javacodegeeks.patterns.adapterpattern.payd.PayD;
import com.javacodegeeks.patterns.adapterpattern.xpay.Xpay;
 
public class RunAdapterExample {
 
    public static void main(String[] args) {
         
        // Object for Xpay
        Xpay xpay = new XpayImpl();
        xpay.setCreditCardNo("4789565874102365");
        xpay.setCustomerName("Max Warner");
        xpay.setCardExpMonth("09");
        xpay.setCardExpYear("25");
        xpay.setCardCVVNo((short)235);
        xpay.setAmount(2565.23);
         
        PayD payD = new XpayToPayDAdapter(xpay);
        testPayD(payD);
    }
     
    private static void testPayD(PayD payD){
         
        System.out.println(payD.getCardOwnerName());
        System.out.println(payD.getCustCardNo());
        System.out.println(payD.getCardExpMonthDate());
        System.out.println(payD.getCVVNo());
        System.out.println(payD.getTotalAmount());
    }
}

В приведенном выше классе сначала мы создали объект Xpay и установили его свойства. Затем мы создали адаптер и передали ему этот объект xpay в своем конструкторе и присвоили его интерфейсу PayD . Статический метод testPayD() принимает тип PayD в качестве аргумента, который запускает и печатает свои методы для тестирования. Поскольку тип, передаваемый в метод testPayD() имеет тип PayD метод выполнит объект без каких-либо проблем. Выше мы передали ему адаптер, который выглядит как тип PayD , но внутри он оборачивает Xpay типа Xpay .

Итак, в проекте Макса все, что нам нужно, — реализовать API-код вендора в коде и передать этот адаптер методу вендора, чтобы заставить платеж работать. Нам не нужно ничего менять в существующем коде.

Рисунок 4

Рисунок 4

4. Класс Адаптер

Существует два типа адаптеров: объектный адаптер и адаптер класса. До сих пор мы видели пример адаптера объекта, который использует композицию объекта, тогда как адаптер класса полагается на множественное наследование для адаптации одного интерфейса к другому. Поскольку Java не поддерживает множественное наследование, мы не можем показать вам пример множественного наследования, но вы можете помнить об этом и реализовать его на одном из ваших любимых объектно-ориентированных языков, таких как c ++, который поддерживает множественное наследование.

Для реализации адаптера класса адаптер должен наследоваться публично от Target и приватно от Adaptee. В результате адаптер будет подтипом Target, но не для Adaptee.

Рисунок 5

Рисунок 5

5. Когда использовать шаблон адаптера

Шаблон адаптера следует использовать, когда:

  1. Существует существующий класс, и его интерфейс не соответствует тому, который вам нужен.
  2. Вы хотите создать повторно используемый класс, который взаимодействует с несвязанными или непредвиденными классами, то есть классами, которые не обязательно имеют совместимые интерфейсы.
  3. Есть несколько существующих подклассов, которые нужно использовать, но нецелесообразно адаптировать их интерфейс путем создания подклассов для каждого из них. Адаптер объекта может адаптировать интерфейс своего родительского класса.

6. Загрузите исходный код

Это был урок по шаблону адаптера. Вы можете скачать исходный код здесь: AdapterPattern-Project