Chuyện kể rằng bạn Bean là một developer và đang phát triển một website e-commerce. Trang web này cho phép người dùng mua và thanh toán online bằng cách dùng một cổng thanh toán của bên thứ 3. Mọi thứ đều nhẹ nhàng như cân đường hộp sữa, cho đến một ngày mưa gió bão bùng… Vào cái ngày định mệnh ấy, PM của bạn Bean thông báo rằng sắp tới website sẽ thay đổi cổng thanh toán và bạn Bean sẽ phải chuẩn bị cho thay đổi này. Để dễ hình dung thì giả sử hệ thống hiện tại đang sử dụng cổng thanh toán Xpay và yêu cầu người dùng nhập các thông tin như sau:
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);
}
Tình trạng code hiện tại:
Và yêu cầu hiện tại là chuyển sang một cổng thanh toán PayD mới yêu cầu các thông số sau:
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);
}
Yêu cầu phát triển:
Cách giải quyết
Bởi vì code hiện tại đang chạy tốt trên production nên bạn Bean không thể nào thay đổi toàn bộ source code, refactor lại chỗ nào dùng Xpay thì viết lại thành PayD. Như vậy Bean sẽ một thứ gì đó để chuyển đổi linh hoạt từ Xpay sang PayD, giống như khi bạn có một dây sạc USB-type A mà điện thoại của bạn chỉ nhận cổng USB-type C thì bạn cần mua thêm một adapter để sử dụng. Tương tự trường hợp hiện tại, bạn Bean cũng cần một adapter giúp chuyển từ cổng Xpay sang PayD.
Adapter design pattern
là mẫu thiết kế cho phép chúng ta điều chỉnh một đối tượng có sẵn sang một đối tượng mong muốn. Đó là mẫu thiết kế hướng về sự linh hoạt sử dụng các interface khác nhau cho cùng một mục đích nào đó mà không cần chỉnh sửa trực tiếp các interface mỗi khi có yêu cầu thay đổi.
Một adapter sẽ sử dụng đối tượng cần thay đổi (Xpay), sau đó dịch nó sang đối tượng mong muốn có (PayD).
Như trong trường hợp của bạn Bean, chúng ta có thể viết một adapter như sau:
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());
}
Adapter impleament theo PayD interface - loại object mong muốn. Và chúng ta thử xem cách sử dụng adapter:
public class RunAdapterExample {
public static void main(String[] args) {
// Object for Xpay
Xpay xpay = new XpayImpl();
xpay.setCreditCardNo("4789565874102365");
xpay.setCustomerName("Tu Bean");
xpay.setCardExpMonth("01");
xpay.setCardExpYear("2020");
xpay.setCardCVVNo((short)123);
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());
}
}
Như vậy chúng ta có thể tạo ra một object kiểu PayD thông qua một contructor có tham số truyền vào là Xpay. Các method bên trong adapter cũng đã giúp chuyển đổi phương thức của Xpay sang các phương thức của PayD. Tuy nhiên thì do các method của 2 loại cổng thanh toán là khác nhau nên đôi khi sự chuyển đổi chỉ mang tính tương đối. Ví dụ như ở trên, cổng PayD chỉ có 1 thông số là CardExpMonthDate
, còn Xpay thì có 2 thông số CardExpMonth
và CardExpYear
. Để tạo ra một cách chuyển đổi linh hoạt, chúng ta tạo ra một method chuyển đổi từ 2 thông số của Xpay sang PayD như sau:
setCardExpMonthDate(this.xpay.getCardExpMonth()+"/"+this.xpay.getCardExpYear());
Giải pháp của chúng ta nhìn tổng quát lại như sau:
Class Adapter
Có 2 loại adapter là object adapter
và class adapter
. Trong ví dụ trên chúng ta đã dùng loại object adapter
, tức là loại adapter sử dụng các thành phần trong object cần chuyển đổi để tạo ra object mong muốn. Trong khi đó, class adapter
sẽ dựa vào đa kế thừa để tạo ra một interface thích ứng với các class khác nhau. Tuy nhiên trong java chúng ta không có đa kế thừa như C++ nên loại này có thể không cần để ý.
Khi nào sử dụng Adapter Pattern
Mẫu thiết kế này được sử dụng khi:
- Bạn đã có sẵn một class nhưng interface của nó thì lại khác phù hợp với cái bạn cần.
- Bạn muốn tạo một class có thể tái sử dụng với các lớp không liên quan hoặc không đoán trước được, các class này không nhất thiết phải có các interface phù hợp trước.