Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.

Problem

Giả sử chúng ta có một ứng dụng theo dõi thị trường chứng khoán. Ứng dụng này cho phép tải dữ liệu chứng khoán từ nhiều nguồn khác nhau dưới dạng XML và hiển thị chúng dưới dạng biểu đồ. Để phân tích dữ liệu, ứng dụng sử dụng một thư viện của bên thứ ba.

Tuy nhiên, thư viện phân tích dữ liệu này chỉ hoạt động với định dạng JSON nên nó không thể làm việc trực tiếp với định dạng XML mà ứng dụng đang sử dụng.

Chúng ta có thể thay đổi code của thư viện để phù hợp với định dạng XML. Tuy nhiên, cách làm này có thể phá vỡ một số đoạn code có sẵn và không phải lúc nào ta cũng có quyền truy cập vào mã nguồn của thư viện.

Solution

Để giải quyết vấn đề trên, ta có thể sử dụng một adapter. Cụ thể, adapter là một thành phần trung gian cho phép các object có interface khác nhau có thể giao tiếp với nhau mà không cần phải thay đổi code của interface có sẵn hoặc của interface đang viết.

Adapter pattern sẽ bao gồm các thành phần sau:

  • Adapted: một interface không tương thích với interface đang viết và cần được tích hợp vào.
  • Target: một interface chứa các phương thức được sử dụng bởi client code (domain specific).
  • 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 viết.
  • Client: implementation của target.

Minh họa:

Có hai cách để triển khai adapter pattern đó là:

  • Object adapter - composition: adapter sẽ chứa tham chiếu đến một hoặc nhiều adapted và đồng thời implement các phương thức của target bằng cách gọi sử dụng các phương thức của các adapted.

  • Class adapter - inheritance: adapter sẽ kế thừa một adapted và đồng thời implement các phương thức của target bằng cách gọi sử dụng các phương thức của adapted.

Sự khác biệt giữa object adapter và class adapter:

  • Object adapter sử dụng composition còn class adapter sử dụng inheritance.
  • Object adapter có thể dùng để gắn kết nhiều adapted còn class adapter chỉ có thể hoạt động với một adapted duy nhất do adapter là một lớp con của adapted.

Object adapter tốt hơn class adapter vì nó cho phép adapter có thể hoạt động với nhiều adapted.

Implementation

Giả sử ta có hai interface là DuckTurkey.

class Duck {
public:
	virtual void quack() = 0;
	virtual void fly() = 0;
};
 
class Turkey {
public:
	virtual void gobble() = 0;
	virtual void fly() = 0;
};

Hai interface này có hai implementation là MallardDuckWildTurkey.

class MallardDuck : public Duck {
public:
	void quack() {
		std::cout << "Quack\n";
	}
 
	void fly() {
		std::cout << "I'm flying\n";
	}
};
 
class WildTurkey : public Turkey {
public:
	void gobble() {
		std::cout << "Gobble gobble\n";
	}
 
	void fly() {
		std::cout << "I'm flying a short distance\n";
	}
};

Nếu ứng dụng của chúng ta chỉ dùng Turkey mà thư viện của bên thứ ba lại dùng Duck thì ta cần phải xây dựng một adapter.

Trước tiên, ta cần xác định các thành phần của adapter pattern:

  • Adapted là interface không tương thích và cần chuyển đổi: Turkey.
  • Target là interface cần chuyển đổi thành: Duck.

Adapter triển khai theo kiểu object adapter sẽ có dạng như sau:

class TurkeyAdapter : public Duck {
private:
	Turkey* _turkey;
 
public:
	TurkeyAdapter(Turkey* turkey) {
		_turkey = turkey;
	}
 
	void quack() {
		_turkey->gobble();
	}
 
	void fly() {
		_turkey->fly();
	}
};

Có thể thấy, adapter thực hiện những nhiệm vụ sau:

  • Giữ một tham chiếu đến adapted.
  • Implement các phương thức của target bằng cách gọi sử dụng các phương thức của adapted.

Applicability

Sử dụng apater pattern khi chúng ta muốn:

  • Sử dụng một class nào đó nhưng interface của nó không phù hợp với code hiện tại.
  • Tái sử dụng một vài class có sẵn mà thiếu các common functionality để có thể được thêm vào superclass.

Pros and Cons

ProsCons
Single responsibility: tách biệt interface hoặc các đoạn code chuyển đổi dữ liệu ra khỏi các đoạn code logicCode phức tạp hơn do cần phải cài đặt thêm các interface và subclass. Đôi khi ta chỉ cần thay đổi code của service class sao cho nó khớp với code cũ là được
Open/closed: có thể thêm vào các adapter mới mà không làm thay đổi client code

Resources