Adapter Design Pattern
Mục đích chính
- Chuyển đổi interface của 1 class thành 1 interface khác mà clients mong muốn. Adapter sẽ giúp các class có interface không tương thích làm việc được với nhau.
- "Wrapper" (Bao bọc) 1 class hiện tại với interface mới.
- Impedance Match giữa 1 thành phần cũ tới hệ thống mới.
Vấn đề quan tâm
Vấn đề đặt ra ở đây là giả sử ta có một thành phần có sẵn ("off the shelf") và ta rất muốn sử dụng lại thành phần này, nhưng nó lại không tương thích với philosophy (triết lý) và kiến trúc (architecture) của hệ thống hiện tại đang được phát triển.
Thảo luận
Việc tái sử dụng như ở mục vấn đề cần giải quyết luôn luôn painful và khó nắm bắt. Khó khăn ở đây là khi thiết kế 1 cái gì đó mới mà phải dùng 1 thành phần cũ thì luôn có điều gì đó không ổn giữa cái cũ và cái mới. Sự không ổn đó có thể đến từ sai khác kích thước vật lý hoặc 1 sự sai lệch nào đó như vấn đề thời gian hoặc đồng bộ hóa. Hoặc cũng có thể không map được do những tiêu chuẩn nhất định.
Lấy ví dụ đơn giản ở đây: ổ cắm điện âm tường của nhà các bạn là ổ cắm 2 chân cũ, bạn mua một cái nồi cơm điện với đầu cắm là 3 chân. Vậy làm thể nào để cắm ổ cắm 3 chân vào ổ 2 chân? Ta cần phải có 1 bộ chuyển đổi và đó chính là Adapter Design Pattern.
Structure
Ở phía dưới ta thấy, phương thức "display()" của class LegacyRectangle mong đợi nhận được các parameters "x,y,w,h" (tọa độ điểm bên trái góc dưới, kích thước hình chữ nhật). Nhưng client lại muốn chuyển tham số đầu vào thành x1, y1, x2, y2 (tạo độ điểm bên trái góc dưới, tọa độ điểm bên phải góc trên). Vậy làm sao để tận dụng phương thức cũ mà không cần viết lại. Sự không nhất quán này có thể được giải quyết bằng cách thêm 1 mức độ gián tiếp bổ sung - tức là 1 đối tượng Adaptor.
Adapter cũng có thể coi là 1 "wrapper".
CODE:
#include <iostream> // Phía dưới là class Rectangle có phương thức "display()"
class LegacyRectangle {
public: void display(int x, int y, int width, int height) { std::cout << "Rectangle with coordinates (" << x << ", " << y << ") and dimensions (" << width << " x " << height << ")\n"; }
}; // Interface cho Adapter
class ShapeAdapter {
public: virtual void display(int x1, int y1, int x2, int y2) = 0;
}; // Adapter để chuyển đổi giữa cách client mong đợi và phương thức "display()" của Rectangle
class RectangleAdapter : public ShapeAdapter {
private: LegacyRectangle rectangle; public: void display(int x1, int y1, int x2, int y2) override { // Chuyển đổi các tham số theo cách mà LegacyRectangle mong đợi int width = x2 - x1; int height = y2 - y1; // Gọi phương thức "display()" của Rectangle rectangle.display(x1, y1, width, height); }
}; // Client sử dụng Adapter để gọi phương thức "display()" của LegacyRectangle mà không cần thay đổi class Rectangle
int main() { ShapeAdapter* adapter = new RectangleAdapter(); adapter->display(1, 2, 5, 7); delete adapter; return 0;
}
Check List
- Xác định những người tham gia: thành phần muốn được hỗ trỡ (tức client), thành phần cần adapt (thích ứng) (tức adaptee).
- Xác định interface mà khách hàng yêu cầu
- Thiết kế một lớp "wrapper" cái mà có thể "impedance match" của adaptee với client
- Trong mẫu thiết kế Adapter, lớp adapter hoặc wrapper thường chứa 1 đối tượng của adaptee: như ở ví dụ trên RectangleAdapter (đối tượng chuyển đổi //adapter) có chứa 1 đối tượng LegacyRectangle (adaptee //đối tượng cần thích ứng)
- Lớp adapter/wrapper "maps" giữa client interface đến adaptee interface
- Máy khách (client uses) được ghép nối với giao diện mới.