06/04/2021, 14:46

Bài 02: Design pattern PHP - Adapter Pattern - Design Pattern PHP

Tiếp tục serie Design pattern PHP thì trong bài này chúng ta tìm hiểu về Adapter Pattern. Loại pattern đặc biệt này khi chương trình phần mềm hoặc ứng dụng website của bạn có phụ thuộc vào các API từ bên ngoài hoặc là sử dụng một số lớp khác có sự thay đổi thường xuyên về tên hàm, tên lớp, ... Đây ...

Tiếp tục serie Design pattern PHP thì trong bài này chúng ta tìm hiểu về Adapter Pattern. Loại pattern đặc biệt này khi chương trình phần mềm hoặc ứng dụng website của bạn có phụ thuộc vào các API từ bên ngoài hoặc là sử dụng một số lớp khác có sự thay đổi thường xuyên về tên hàm, tên lớp, ... Đây là một loại pattern thuộc nhóm mô hình cấu trúc (Structural patterns) bởi vì nó cho chúng ta cách code mở rộng và dễ quản lý code hơn.

1. Tại sao cần sử dụng Adapter pattern

Khi các bạn làm việc nhóm thì việc đặt ra quy tắc code là điều nên làm vì sẽ thống nhất hơn. Tuy nhiên trong thực tế đôi lúc sẽ có những thay đổi đột xuất và bạn sẽ mất cả khối thời gian để xử lý thay đổi đó, điều này thật nhàm chán. Để rõ hơn thì ta có một ví dụ dưới đây.

Giả sử xếp giao cho bạn một lớp dùng để xử lý thanh toán online Bảo Kim như sau:

class Baokim {

    public function __construct() {
        // Hàm khởi tạo
    }

    public function sendPayment($amount){
        // Code xử lý gửi thanh toán tại đây
    }
}

Cách sử dụng lớp này cũng tương đối đơn giản bạn chỉ cần tạo mới và sử dụng như sau:

$baokim = new Baokim();
$baokim->sendPayment(1234);

Có một điều chắc chắn rằng trong ứng dụng của bạn sẽ phải sử dụng nhiều hơn một lần lớp này, như vậy ban sẽ phải tạo mới và gọi đến phương thức sendPayment() nhiều lần.

Bây giờ ta có tính huống như sau, sau khoảng thời gian 1 tuần, khi mà bạn đã gần như làm xong dự án và xếp bảo là lớp Baokim đã được chỉnh sửa lại tên của phương thức sendPayment() sang doPayment(). Ôi trời ơi, lúc này bạn sẽ phải quay lại code và sửa tất các các vị trí mà bạn đã sử dụng cheeky

Để giải quyết vấn đề này thì ta sử dụng kiểu Adapter Pattern. Ý tưởng của nó là khi có sự thay đổi của lớp khác (API)  đang sử dụng thì ta chỉ sửa đúng 1 chỗ thôi thay vì phải đi vào từng file để sửa. Và đây là các bước làm:

  • Coi lớp Baokim là một class gốc bên xếp gửi sang
  • Tạo ra một interface paymentAdapter
  • Tạo ra lớp baokimAdapter implements paymentAdapter

// Lớp gốc
class Baokim {

    public function __construct() {
        // Hàm khởi tạo
    }

    public function sendPayment($amount){
        // Code xử lý gửi thanh toán tại đây
        echo $amount;
    }
}

// Tạo một interface để các adapter không có quyền đổi tên hàm 
interface paymentAdapter{
    public function pay($amount);
}

// Lớp adapter sử dụng
class baokimAdapter implements paymentAdapter{
    private $__baokim;
    public function __construct(Baokim $baokim) {
        $this->__baokim = $baokim;
    }
    
    public function pay($amount){
        $this->__baokim->sendPayment($amount);
    }
}

// Cách dùng
$baokimAdapter = new baokimAdapter(new Baokim());
$baokimAdapter->pay(1234);

Như vậy mỗi khi sử dụng thì thay vì sử dụng lớp Baokim thì ta sử dụng baokimAdapter. Riêng paymentAdapter là một interface tạo ra nhằm mục đích không cho ta sửa tên hàm pay() (đọc bài template interface php).

Bây giờ xếp bảo lớp Baokim thay đổi phương thức sendPayment thành doPayment thì thay vì vào từng dòng code sửa thì ta chỉ cần sửa nó ở hàm pay() trong lớp baokimAdapter.

class baokimAdapter implements paymentAdapter{
    private $__baokim;
    public function __construct(Baokim $baokim) {
        $this->__baokim = $baokim;
    }
    
    public function pay($amount){
        $this->__baokim->doPayment($amount);
    }
}

2. Thêm một adapter khác

Giả sử ta có thêm một chức năng gửi tiền đặt phòng, xếp sẽ gửi qua một lớp như sau:

class Booker {

    public function __construct() {
        // Hàm khởi tạo
    }

    public function paymentRoom($amount){
        // Code xử lý gửi thanh toán tại đây
        echo $amount;
    }
}

Ok, ta tiếp tục làm như ví dụ trên như sau:

// Lớp gốc
class Booker {

    public function __construct() {
        // Hàm khởi tạo
    }

    public function paymentRoom($amount){
        // Code xử lý gửi thanh toán tại đây
        echo $amount;
    }
}

// Tạo một interface để các adapter không có quyền đổi tên hàm 
interface paymentAdapter{
    public function pay($amount);
}

// Lớp adapter sử dụng
class bookerAdapter implements paymentAdapter{
    private $__booker;
    public function __construct(Booker $booker) {
        $this->__booker = $booker;
    }
    
    public function pay($amount){
        $this->__booker->paymentRoom($amount);
    }
}

// Cách dùng
$bookerAdapter = new bookerAdapter(new Booker());
$bookerAdapter->pay(1234);

Như vậy ta sử dụng chung interface paymentAdapter cho cả hai trường hợp trên.

3. Lời kết

Adapter Design pattern thực sự rất hữu ích khi bạn code với ứng dụng lớn có sử dụng nhiều API từ bên ngoài, nó giúp bạn giảm thiểu tối đa nhưng thay đổi từ nhà cung cấp API. Nhìn thì thực sự nó hơi phức tạp vì phải tạo ra nhiều lớp và interface khác nhau nhưng nếu hệ thống lớn thì nó lại có rất nhiều hữu ích.

Bài tiếp
0