Design Patterns: Mẫu Apapter
() translation by (you can also view the original English article)
Trong bài trước "Facade Pattern", chúng ta đã xem qua thiết kế Facade để có thể đơn giản hóa vấn đề của bất kỳ hệ thống lớn và phức tạp nào chỉ bằng cách sử dụng một class facade đơn giản.
Trong bài này, chúng ta sẽ tiếp tục thảo luận về design patterns với adapter design pattern. Cụ thể mẫu này có thể được sử dụng khi mã nguồn của bạn phụ thuộc vào một vài API bên ngoài, hay những class khác mà có xu hướng thay đổi thường xuyên. Mẫu này thuộc mục của "Structure Patterns" bởi vì nó dạy chúng ta cấu trúc của mã nguồn và các các class để quản lý (hoặc) mở rộng một cách dễ dàng.
Lần nữa, tôi xin nhắc lại rằng các design patterns không có gì mới qua các class truyền thống. Thay vào đó, họ chỉ cho chúng ta cách sắp xếp các class, kiểm soát hành vi của chúng, và quản lý chúng.
Vấn đề
1 |
<?php
|
2 |
class PayPal { |
3 |
|
4 |
public function __construct() { |
5 |
// Your Code here //
|
6 |
}
|
7 |
|
8 |
public function sendPayment($amount) { |
9 |
// Paying via Paypal //
|
10 |
echo "Paying via PayPal: ". $amount; |
11 |
}
|
12 |
}
|
13 |
|
14 |
$paypal = new PayPal(); |
15 |
$paypal->sendPayment('2629'); |
Đoạn mã ở trên bạn nhìn thấy rằng chúng ta đang sử dụng class Paypal để dễ dàng thanh toán số tiền. Ở đây, chúng ta đang tạo trực tiếp một đối tượng của class PayPal và thành toán thông qua PayPal. Đoạn mã này thêm vào ở nhiều class. Vì thề chúng ta thấy đoạn mã đang sử dụng phương thức $paypal->sendPayment('amount here');
để thanh toán.
Trước đây, PayPal đã thay đổi tên phương thức API từ sendPayment
đến payAmount
. Điều này chỉ ra vấn đề một cách rõ ràng cho những người đã từng sử dụng phương thức sendPayment
. Một cách cụ thể, chúng ta cần thay đổi tất cả phương thức gọi sendPayment
đến payAmount
. Thử tưởng tượng số tiền cho đoạn mã chúng ta cần thay đổi và thời gian chúng ta bỏ ra để kiểm tra trên mỗi chức năng một lần nữa.
Giải pháp
Một giải pháp cho vấn đề này là sử dụng mô hình thiết kế "adapter".
Theo Wikipedia:
Trong công nghệ phần mềm, mô hình "adapter" là mô hình thiết kế phần mềm theo hướng "inferface" của một class đã tồn tại được sử dụng từ một "interface" khác. Mô hình "Adapter" thường xuyên được sử dụng các class tồn tại làm việc với các class khác mà không chỉnh sửa mã nguồn của chúng.
Trong trường hợp này, chúng ta nên tạo một "wrapper interface" đây là điều có thể làm. Chúng ta không thể thay đổi bất cứ cái gì từ thư viện bên ngoài, bởi vì chúng ta không có quyền kiểm soát nó và nó sẽ thay đổi bất cứ lúc nào.
Còn bây giờ hãy đi sâu vào đoạn mã, thể hiện "adapter" patter trong hành động:
1 |
// Concrete Implementation of PayPal Class
|
2 |
class PayPal { |
3 |
|
4 |
public function __construct() { |
5 |
// Your Code here //
|
6 |
}
|
7 |
|
8 |
public function sendPayment($amount) { |
9 |
// Paying via Paypal //
|
10 |
echo "Paying via PayPal: ". $amount; |
11 |
}
|
12 |
}
|
13 |
|
14 |
// Simple Interface for each Adapter we create
|
15 |
interface paymentAdapter { |
16 |
public function pay($amount); |
17 |
}
|
18 |
|
19 |
class paypalAdapter implements paymentAdapter { |
20 |
|
21 |
private $paypal; |
22 |
|
23 |
public function __construct(PayPal $paypal) { |
24 |
$this->paypal = $paypal; |
25 |
}
|
26 |
|
27 |
public function pay($amount) { |
28 |
$this->paypal->sendPayment($amount); |
29 |
}
|
30 |
}
|
Học đoạn mã ở trên và bạn thấy chúng ta không làm thay đổi bất cứ thứ gì trong class chính của PayPal
. Thay vào đó chúng ta có tạo một "interface" cho "adapter" thanh toán của chúng ta và một "adapter" class cho PayPal.
Tiếp theo đó chúng ta tạo một đối tượng của class "adapter" thay vì của class chính PayPal
Trong khi tạo một đối tượng của "adapter" chúng ta sẽ truyền đối tượng của class chính PayPal
như một đối số. vậy class "adapter" có thể tham chiếu đến class chính và nó có thể gọi các phương thức của class chính PayPal
.
Nào hãy tìm hiểu trực tiếp tại phương thức này, xem chúng hoạt động như thế nào?
1 |
// Client Code
|
2 |
$paypal = new paypalAdapter(new PayPal()); |
3 |
$paypal->pay('2629'); |
Bây giờ tưởng tượng PayPal thay đổi tên phương thức của nó từ sendPayment
đến PayAmount. Vậy chúng ta chỉ cần thay đổi đổi trong paypalAdapter
. Nhìn vào mã "adapter" đã chỉnh sửa, chi có một thay đổi ở đây.
1 |
class paypalAdapter implements paymentAdapter { |
2 |
|
3 |
private $paypal; |
4 |
|
5 |
public function __construct(PayPal $paypal) { |
6 |
$this->paypal = $paypal; |
7 |
}
|
8 |
|
9 |
public function pay($amount) { |
10 |
$this->paypal->payAmount($amount); |
11 |
}
|
12 |
}
|
Vậy chỉ có một thay đổi và chúng ta đã làm nó.
Thêm một Adapter mới
Tại đây, chúng ta đã nhìn thấy chúng ta có thể sử dụng "adapter" pattern như thế nào thông qua kịch bản đề cập ở trên. Bây giờ, thật dễ để tạo một class mới phụ thuộc vào "adapter" đã tồn tại. Giả sử dùng API MoneyBooker để thanh toán.
Thay vì sử dụng trực tiếp class "MoneyBooker", chúng ta nên áp dụng giống "adapter" pattern chúng ta vừa mới sử dụng cho PayPal.
1 |
// Concrete Implementation of MoneyBooker Class
|
2 |
class MoneyBooker { |
3 |
|
4 |
public function __construct() { |
5 |
// Your Code here //
|
6 |
}
|
7 |
|
8 |
public function doPayment($amount) { |
9 |
// Paying via MoneyBooker //
|
10 |
echo "Paying via MoneyBooker: ". $amount; |
11 |
}
|
12 |
}
|
13 |
|
14 |
// MoneyBooker Adapter
|
15 |
class moneybookerAdapter implements paymentAdapter { |
16 |
|
17 |
private $moneybooker; |
18 |
|
19 |
public function __construct(MoneyBooker $moneybooker) { |
20 |
$this->moneybooker = $moneybooker; |
21 |
}
|
22 |
|
23 |
public function pay($amount) { |
24 |
$this->moneybooker->doPayment($amount); |
25 |
}
|
26 |
}
|
27 |
|
28 |
// Client Code
|
29 |
$moneybooker = new moneybookerAdapter(new MoneyBooker()); |
30 |
$moneybooker->pay('2629'); |
Bạn có thể nhìn thấy là cùng một nguyên tắc đã áp dụng. Bạn định nghĩa một phương thức chấp nhật class "third-party. Nếu "dependency" API của nó thay đổi, bạn chỉ cần thay đổi "dependent" class mà không chỉnh sửa "interface" bên ngoài.
Ưu điểm
Một ứng dụng tuyệt vời đó là móc cố định vào các thư viện và API khác. Vì vậy tôi đề xuất rằng chúng ta thực thiện phương thức "adapter", vì thế chúng ta không gặp bất kỳ rắc rồi nào khi API hay thư viện của "third-party" thay đổi mã nguồn cơ sở của nó.
Tôi đã cố gắng hết sức để cung cấp một ví dụ cơ bản và hữu ích để minh họa "adapter" pattern, nhưng nếu bạn có ý kiến khác hay câu hỏi nào, đừng ngần ngại thêm vào ở phía dưới.