Adapter Pattern là gì và cách sử dụng trong PHP

Adapter Pattern là gì và cách sử dụng trong PHP

Trong bài viết trước, chúng ta đã cùng nhau tìm hiểu về Object Pool Pattern. Trong bài viết này, chúng ta hãy cùng nhau tìm hiểu về design pattern đầu tiên thuộc nhóm cấu trúc (structural patterns) mang tên “Adapter Pattern” nhé!

Adapter Pattern là gì thế nhỉ?

Adapter pattern là một loại design pattern thuộc nhóm cấu trúc (structural patterns). Không phải một cách ngẫu nhiên mà loại design pattern này lại mang tên là Adapter Pattern. Trong tiếng Anh, từ adapt có nghĩa là “thích nghi với, làm cho hợp với, sửa cho hợp với”. Với từ khóa này không biết nhiều bạn đã suy đoán được chức năng của loại design pattern này chưa nhỉ?

Về bản chất, Adapter pattern là loại design pattern sử dụng một thành phần trung gian được gọi là Adapter, đối tượng này có nhiệm vụ chuyển đổi từ một giao diện (interface) có sẵn thành một giao diện khác đảm bảo thích hợp với lớp đang viết.

Ứng dụng của Adapter Pattern trong thực tế

Trong thực tế cuộc sống có thể chúng ta đã và đang dễ dàng bắt gặp được những ví dụ có liên quan đến loại design pattern này mà không hề hay biết, ví dụ:

  • Chiếc smartphone của bạn đang sử dụng nguồn điện sạc là 3V trong khi nguồn điện lưới lại là 220V. Sẽ như thế nào nếu chúng ta truyền thẳng nguồn điện 220V vào cho nó. Chắc các bạn cũng dễ dàng đoán được kết quả rồi. Chính vì sự chênh lệch này nên chúng ta sẽ sử dụng một thành phần adapter trung gian mà ở đây chính là chiếc sạc điện thoại của các bạn, adapter này có nhiệm vụ chuyển từ nguồn điện 220V kia về nguồn điện 3V để có thể sạc vào cho chiếc smartphone kia một cách an toàn.

the Adapter Pattern

  • Bạn mới mua một chiếc màn hình máy tính mới và phích cắm của màn hình này có 3 chân trong khi ổ điện ở nhà bạn chỉ có mỗi 2 lỗ. Làm thế nào để có thể cắm được chiếc phích kia vào đây? Đơn giản, bạn chỉ cần sử dụng một thành phần adapter trung gian mà ở đây chính là bộ chuyển từ 3 chân sang 2 chân, cắm xong đâu vào đấy là chiếc màn hình kia đã vào điện và hiển thị ngay lập tức, tuyệt vời ông mặt trời.

the Adapter Pattern

Tại sao cần sử dụng Adapter Pattern vậy nhỉ?

Trong quá trình phát triển phần mềm, việc sử dụng các API (Application Programming Interface) cũng như các dịch vụ (services) từ bên thứ ba là không thể tránh khỏi. Việc các API cũng như các dịch vụ này được phía nhà cung cấp cập nhật phiên bản thường xuyên cũng là điều dễ gặp và dễ hiểu.

Đối với các thay đổi nhỏ, khả năng tên của các hàm của API hoặc dịch vụ bị thay đổi có thể không cao. Tuy nhiên, sẽ như thế nào nếu một ngày đẹp trời nào đó API hoặc dịch vụ có một thay đổi lớn (breaking change) và hầu như tất cả các tên hàm ở phiên bản cũ mà bạn đang sử dụng đều bị thay đổi và bạn đã vô tình gọi những tên hàm cũ này ở hàng ngàn tập tin khác nhau trong dự án của mình. Bạn chẹp môi, nếu vậy thì ráng đầu tư vài tuần hoặc vài tháng chỉnh lại tên cho tương ứng là được rồi.

Nói rồi bạn thử thay đổi lại code, trong quá trình sửa đổi code bạn vô tình nhận ra không phải chỉ đơn thuần sửa đổi lại tên của các hàm là xong mà logic của code cũ cũng đã bị ảnh hưởng và làm cho chương trình chạy lỗi. Trong lúc dầu sôi lửa bỏng, vài tuần sau phía nhà cung cấp lại ra thông báo cập nhật phiên bản tiếp theo cho API và tên tất cả các hàm lại bị thay đổi. Bạn thẫn thờ và bế tắc, bạn cảm giác như cuộc đời sửa code dạo của mình dường như đang trở nên tăm tối hơn bao giờ hết.

Đó chính là lý do mà Adapter Pattern đã ra đời để giải cứu thế giới và cứu rỗi cuộc đời của lập trình viên chúng ta.

Khi nào nên sử dụng Adapter Pattern vậy nhỉ?

Dưới đây là một số trường hợp chúng ta có thể cân nhắc sử dụng Adapter Pattern để giải quyết vấn đề:

  • Sử dụng Adapter Pattern khi bạn muốn sử dụng một lớp đã tồn tại trước đó nhưng interface của nó lại không phù hợp với nhu cầu sử dụng của bạn.
  • Sử dụng Adapter Pattern khi bạn muốn code của mình không bị ảnh hưởng từ các thay đổi hoặc các lần cập nhật phiên bản mới từ API hoặc dịch vụ từ bên thứ ba (thay đổi tên hàm, tên lớp,…)
  • Sử dụng Adapter Pattern khi bạn muốn tạo ra một thành phần trung gian vừa đảm bảo kết nối và phối hợp với API hoặc dịch vụ từ bên thứ ba, vừa cung cấp các hàm tiện ích đáp ứng phù hợp với nhu cầu sử dụng của mình.

Cấu trúc và phân loại Adapter Pattern

Cấu trúc của Adapter Pattern

Adapter Pattern bao gồm bốn thành phần cơ bản là: Adaptee, Adapter, TargetClient.

Trong đó:

  • Adaptee: Định nghĩa interface không tương thích, cần được tích hợp vào.
  • Adapter: Lớp tích hợp, giúp interface không tương thích tích hợp được với interface đang làm việc. Thực hiện việc chuyển đổi interface cho Adaptee và kết nối Adaptee với Client.
  • Target: Một interface chứa các phương thức mà Client cần sử dụng.
  • Client: Đây là lớp sẽ sử dụng các đối tượng implements từ interface Target.

Phân loại Adapter Pattern

Trong hướng đối tượng có hai khái niệm quan trọng luôn song hành cùng nhau, đó là:

  • Composition: Cấu thành, nghĩa là một lớp B nào đó sẽ trở thành một thành phần của lớp A (một thuộc tính của lớp A). Mặc dù lớp A không kế thừa lại giao diện của lớp B nhưng nó có được mọi khả năng mà lớp B có.
  • Inheritance: Kế thừa, nghĩa là một lớp con sẽ kế thừa từ lớp cha và thừa hưởng tất cả những gì lớp cha của nó có. Kế thừa giúp tăng khả năng tái sử dụng code, tăng khả năng bảo trì và nâng cấp chương trình về sau. Tuy nhiên việc quá lạm dụng kế thừa cũng sẽ khiến cho chương trình của chúng ta phức tạp một cách không cần thiết. Do vậy các lập trình viên thường có khuynh hướng sử dụng composition hơn.

Ứng với hai khái niệm trên, ta có hai cách để cài đặt lớp Adapter là Object AdapterClass Adapter. Trong đó:

  • Object Adapter:

Trong mô hình này, một lớp mới (Adapter) sẽ tham chiếu đến một (hoặc nhiều) đối tượng của lớp có sẵn với interface không tương thích (Adaptee), đồng thời cài đặt interface mà người dùng mong muốn (Target). Trong lớp mới này, khi cài đặt các phương thức của interface người dùng mong muốn, sẽ gọi phương thức cần thiết thông qua đối tượng thuộc lớp có interface không tương thích.

the Adapter Pattern

  • Class Adapter:

Trong mô hình này, một lớp mới (Adapter) sẽ kế thừa lớp có sẵn với interface không tương thích (Adaptee), đồng thời cài đặt interface mà người dùng mong muốn (Target). Trong lớp mới, khi cài đặt các phương thức của interface người dùng mong muốn, phương thức này sẽ gọi các phương thức cần thiết mà nó thừa kế được từ lớp có interface không tương thích.

the Adapter Pattern

Tại sao Object Adapter lại tốt hơn?

Object Adapter sử dụng composition để giữ một thể hiện của Adaptee. Việc sử dụng composition để giữ một thể hiện của Adaptee cho phép một Adapter hoạt động với nhiều Adaptee nếu cần thiết.

Trong khuôn khổ bài viết này, mình cũng sẽ sử dụng Object Adapter để minh họa về cách cài đặt của Adapter Pattern nhé.

Ví dụ minh họa về Adapter Pattern

Giả sử hiện tại bạn được yêu cầu phát triển một ứng dụng cho phép trả về tốc độ tối đa của các loại xe hơi trên toàn thế giới được đo bằng đơn vị ki-lô-mét trên giờ (kph). Hiện đã có sẵn một dịch vụ từ bên thứ ba cho phép trả về tốc độ tối đa của tất cả các loại xe hơi trên toàn thế giới, tuy nhiên đơn vị chuẩn được dịch vụ này sử dụng lại là dặm trên giờ (mph). Ý tưởng để giải quyết vấn đề này là tạo ra một thành phần trung gian cho phép chuyển từ đơn vị km/h sang đơn vị mph. Chúng ta vừa được giới thiệu về Adapter Pattern rồi, cùng thử áp dụng Adapter Pattern xem có giải quyết được bài toán này không nhé!

Bước 1: Tạo ra interface MovableAdaptee

Interface này tương ứng với thành phần Adaptee trong cấu trúc của Adapter Pattern. Nó chứa phương thức getSpeed() cho phép trả về tốc độ tối đa của các loại xe hơi được đo bằng đơn vị dặm trên giờ (mph).

<?php
public interface MovableAdaptee
{
     // returns speed in mph
     public function getSpeed();
}
?>

Bước 2: Tạo ra một concrete implementation từ interface này mang tên RollsRoyce

<?php
public class RollsRoyce implements MovableAdaptee
{
     public function getSpeed()
{
          return 155;
     }
}
?>

Bước 3: Tiến hành tạo ra interface MovableTarget

Interface này tương ứng với thành phần Target trong cấu trúc của Adapter Pattern. Nó chứa phương thức getSpeed() cho phép trả về tốc độ tối đa của các loại xe hơi được đo bằng đơn vị ki-lô-mét trên giờ (kph) – Thứ mà chúng ta đang mong muốn.

<?php
public interface MovableTarget
{
     // returns speed in kph
     public function getSpeed();
}
?>

Bước 4: Tạo ra class MovableAdapter implements từ interface MovableTarget

Đây có thể nói là class quan trọng nhất giúp kết nối giữa Adaptee và Client. Nội dung cài đặt của nó như sau:

<?php
public class MovableAdapter implements MovableTarget {
     private $adaptee;

     public function __construct($adaptee)
{
          $this->adaptee = $adaptee;
     }

     public function getSpeed()
{
          return convertMPHToKPH($adaptee->getSpeed());
     }

     public function convertMPHToKPH($mph)
{
          return $mph * 1.60934;
     }
}
?>

Vậy là quá trình cài đặt đã xong, cùng chạy thử xem chúng ta thu được kết quả cuối cùng như thế nào nhé:

<?php
$rollsRoyce = new RollsRoyce();
$rollsRoyceAdapter = new MovableAdapter($rollsRoyce);
echo "The maximum speed of Rolls-Royce in kph: " . $rollsRoyceAdapter->getSpeed();
?>

Kết quả cuối cùng thu được:

The maximum speed of Rolls-Royce in kph: 249.4477

Như vậy là chúng ta đã áp dụng thành công Adapter Pattern để giải quyết vấn đề đặt ra rồi đấy. Khá nhanh chóng và tuyệt vời đúng không nào.

Ưu và nhược điểm của Adapter Pattern

Ưu điểm

  • Adapter Pattern cho phép nhiều đối tượng có giao diện giao tiếp khác nhau có thể tương tác và giao tiếp với nhau.
  • Adapter Pattern giúp code của chúng ta không bị ảnh hưởng từ các thay đổi hoặc các lần cập nhật phiên bản mới từ API hoặc dịch vụ từ bên thứ ba (thay đổi tên hàm, tên lớp,…)

Nhược điểm

  • Trong quá trình cài đặt Adapter Pattern có thể dẫn đến trường hợp tồn tại một chuỗi các Adapter dẫn đến việc gia tăng thêm một ít chi phí.
  • Việc sử dụng Adapter Pattern có thể khiến cho code của chương trình trở nên nhiều hơn và phức tạp hơn. Nguyên nhân là do đòi hỏi phải sử dụng nhiều lớp mới có thể cài đặt được loại design pattern này.

Kết luận

Như vậy trong bài viết này, chúng ta đã cùng nhau tìm hiểu về loại design pattern đầu tiên thuộc nhóm cấu trúc mang tên Adapter Pattern. Hy vọng loại design pattern này sẽ giúp ích cho các bạn trong tương lai. Trong bài viết tiếp theo của series, mình sẽ tiếp tục giới thiệu cho các bạn một design pattern tiếp theo thuộc nhóm cấu trúc mang tên “Bridge Pattern”. Các bạn nhớ đón xem nhé!

Gửi phản hồi