Adapter Pattern

Mở đầu

Khái niệm

Trước tiên, nhắc đến Adapter Pattern chúng ta sẽ tìm hiểu một chút về khái niệm của nó.

Adapter Pattern được định nghĩa là một mẫu thiết kế cho phép bạn sửa đổi một giao diện giữa đối tượng và một lớp mà không phải sửa đổi trực tiếp lên chúng. Hay nói ngắn gọn nó là một bộ chuyển đổi.

Tại sao nên và cần sử dụng Adapter Pattern ?

Như chúng ta biết, trong ứng dụng do cá nhân chúng ta hoặc 1 team phát triển đôi khi vẫn cần dùng các API hay thư viện từ bên ngoài. Việc các thư viện/API đó bị sửa đổi theo các version là điều dễ gặp.

Vậy hãy tưởng tượng 1 hàm trong API mà bạn đang dùng khi update lên version mới bỗng nhiên bị đổi tên? Mà trong ứng dụng đã có quá nhiều nơi sử dụng đến hàm này, vậy sẽ là một vấn đề khá nghiêm trọng và mất thời gian nếu đây là một ứng dụng lớn. Vì dĩ nhiên bạn sẽ phải lật lại từng dòng code đã sử dụng để sửa lại tên hàm. Việc này đôi khi chúng ta vẫn nghĩ sẽ ổn đối với các ứng dụng nhỏ vì cũng chỉ mất chút ít thời gian nhưng sẽ ra sao nếu version tiếp theo họ lại tiếp tục thay đổi? Quá rắc rối và phiền thức! Thậm chí trong lúc thay đổi những dòng mã đó còn sinh ra các lỗi không đáng có khác.

Ok, đừng lo lắng về điều đó 😄, vấn đề sẽ được giải quyết nếu bạn sử dụng Adapter Pattern, hãy cùng tìm hiểu qua ví dụ dưới đây.

Ví dụ

Giả sử chúng ta sử dụng API có lớp quản lý tên khách hàng được cung cấp bởi công ty Y nào đó Version 1.0 lớp này tên là User chỉ có 2 hàm

  • setName()
  • getName()

Sau khi update lên phiên bản 2.0, công ty này quyết định đổi lại với tên Customer và lúc này có đến 4 hàm

  • setFirstName()
  • getFirstName()
  • setLastName()
  • getLastName()

Vì vậy, nếu bạn muốn update lên 2.0 thì cần phải có bộ chuyển đổi để đảm bảo rằng sau khi cập nhật, đối tượng User được xây dựng từ hệ thống cũ vẫn được xử lý

Vậy việc chúng ta cần làm là xây dựng Adapter để mở rộng 2 hàm cũ thành 4 hàm mới. Lớp User ban đầu với 2 hàm get/set

Tạo đối tượng User

Interface

<?php
interface UserInterface
{
	public function setName($name);
    public function getName();
}

Implement class

<?php
class User
{
	private $name;

	public function setName($name)
    {
    	$this->name = $name;
    }

    public function getName()
    {
    	return $this->name;
    }
}

Tạo đối tượng Customer

Interface

<?php
interface UserInterface
{
	public function setFirstName($fname);
    public function getFirstName();
    public function setLastName($lname);
    public function getLastName();
}

Implement class

<?php
class Customer implements CustomerInterface
{
	private $firstName;
    private $lastName;

	public function setFirstName($fname)
    {
    	$this->firstName = $fname;
    }

    public function getFirstName()
    {
    	return $this->firstName;
    }

    public function setLastName($lname)
    {
    	$this->lastName = $lname;
    }

    public function getLastName()
    {
    	return $this->lastName;
    }
}

Ok, vậy là chúng ta đã có hai đối tượng cần thiết, bây giờ cần bộ chuyển đổi (Adapter) để gắn đối tượng User vào sử dụng được ở version 2.0

Tạo bộ chuyển đổi

Ở đây mình đặt tên là UserToCustomerAdapter

<?php
class UserToCustomerAdapter implements CustomerInterface
{
	//Sử dụng để lưu trữ object User khi chuyển đổi
	protected $user;

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

Điểm khác biệt giữa đối tượng User cũ và Customer phiên bản mới là đối tượng User chứa tên khách hàng là một chuỗi bao gồm cả họ tên, trong khi đối tượng Customer tách riêng họ và tên. Vậy để chuyển đổi giữa đối tượng User và Customer, ta tách phần tên đầy đủ trong lớp User cũ thành 2 phần là tên và họ.

Phần tên đầy đủ có thể lấy được từ hàm getName() của objectUser

<?php
class UserToCustomerAdapter implements CustomerInterface
{
	//Sử dụng để lưu trữ object User khi chuyển đổi
	protected $user;

    protected $firstName;
    protected $lastName;
    public function __construct(User $user)
    {
		$this->user = $user;
        // Lấy fullname từ object cần chuyển đổi (User)
        $fullname = $this->user->getName();
        //Tách chuỗi
        $pieces = explode(" ", $fullname);
        $this->firstName = $pieces[0];
        $this->lastName = $pieces[1];
    }
}

Bây giờ chúng ta đã có tên và họ của khách hàng. Để tương thích với đối tượng Customer, cần phải hiện thực các hàm mới của Customer như setFirstName, setLastName, getFirstName và getLastName. Như sau:

<?php
class UserToCustomerAdapter implements CustomerInterface
{
    protected $user;

    protected $firstName;
    protected $lastName;
    public function __construct(User $user)
    {
	$this->user = $user;
        $fullname = $this->user->getName();

        $pieces = explode(" ", $fullname);
        $this->firstName = $pieces[0];
        $this->lastName = $pieces[1];
    }

    public function setFirstName($fname)
    {
        $this->firstName = $fname;
    }

    public function getFirstName()
    {
        return $this->firstName;
    }

    public function setLastName($lname)
    {
        $this->lastName = $lname;
    }

    public function getLastName()
    {
        return $this->lastName;
    }
}

Như vậy chúng ta đã có một bộ chuyển đổi. Vậy nó sẽ làm việc ra sao? Test thử thôi :>

Test Adapter

Để có cái nhìn tổng quát về cách thức làm việc, tôi sẽ tách đoạn mã ra

Đầu tiên tạo một object User và setName()

<?php
$user = new User;
$user->setName("Thanh Meo");

Sau đó chuyển đối tượng User sang cho đối tượng UserToCustomerAdapter

<?php
$user = new User;
$user->setName("Thanh Meo");
//Chuyển đổi
$adapter = new UserToCustomerAdapter($user);

Và lúc này, ta có thể sử dụng các hàm của Customer như getFirstName, ```setLastName`` một cách bình thường...

<?php
$user = new User;
$user->setName("Thanh Meo");
$adapter = new UserToCustomerAdapter($user);
//Sử dụng
$firstName = $adapter->getFirstName();
$lastName = $adapter->getLastName();
echo "Customer's first name: {$firstName}, last name: {$lastName)";

Kết quả nhận được

Customer's firstname: Thanh, last name: Meo

Đó là cách thức làm việc của mẫu Adapter, sử dụng đối tượng Customer từ đối tượng User ở phiên bản cũ nhờ bộ chuyển đổi 😄

Kết

Việc sử dụng các pattern trong lập trình là rất hữu ích. Mẫu Adapter mình giới thiệu đối với các ứng dụng sử dụng nhiều API từ bên ngoài hoặc hệ thống lớn sẽ giúp giảm thiếu tối đa việc thay đổi mã nguồn, ngoài ra các đoạn mã cũng dễ dàng quản lý, bảo trì hay mở rộng hơn.

Tham khảo: Sách Design Patterns for Dummies


All Rights Reserved