Mẫu thiết Adapter giữ vai trò trung gian giữa hai lớp, chuyển đổi giao diện của một hay nhiều lớp có sẳn thành một giao diện khác, tương thích với lớp đang viết. Adapter giải quyết vấn đề không tương thích của hai giao diện, điều này cho phép các lớp có các giao diện khác nhau có thể dể dàng giao tiếp tốt với nhau thông qua giao diện chuyển tiếp trung gian, không cần thay đổi mã nguồn của lớp có sẳn cũng như lớp đang viết.
Mẫu thiết kế Adapter còn gọi là Wrapper do cung cấp một giao diện “bọc ngoài” tương thích cho một hệ thống có sẳn, hệ thống có sẳn này có dữ liệu và hành vi phù hợp nhưng có giao diện không tương thích với lớp đang viết.
1. Cài đặt
Có hai cách để cài đặt mẫu thiết kế Adapter:
– Tiếp hợp lớp (dùng thừa kế – inheritance): một lớp mới (Adapter) sẽ kế thừa lớp có sẳn với giao diện không tương thích (Adaptee), đồng thời cài đặt giao diện 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 giao diện 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ó giao diện không tương thích. Tiếp hợp lớp đơn giản nhưng dễ gặp trường hợp đụng độ (trùng) tên phương thức.
– Tiếp hợp đối tượng (dùng tích hợp – composition): 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 giao diện không tương thích (Adaptee), đồng thời cài đặt giao diện 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 giao diện 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ó giao diện không tương thích. Tiếp hợp lớp tránh được vấn đề đa thừa kế, không có trong các ngôn ngữ hiện đại (Java, C#).
Các thành phần tham gia:
- Target: định nghĩa giao diện Client đang làm việc (phương thức request()).
- Adaptee: định nghĩa giao diện không tương thích (phương thức adaptedOperation()), cần được tiếp hợp để sử dụng được.
- Adapter: lớp tiếp hợp, giúp giao diện đang làm việc tiếp hợp được với giao diện không tương thích. Cài đặt phương thức request() bằng cách gọi adaptedOperation() của Adaptee. Adapter có thể chứa mã bổ sung để phù hợp với nhu cầu của Client.
package demoadapter; interface Target { String estimate(int i); } class Adaptee { public double precise(double a, double b) { System.out.println("Old lib::precise"); return a/b; } } class Adapter extends Adaptee implements Target { @Override public String estimate(int i) { return String.valueOf(Math.round(precise(i, 3))); } } public class DemoAdapter { public static void main(String[] args) { System.out.println("--- Adapter Pattern ---"); Target target = new Adapter(); System.out.println(target.estimate(5)); } }
DemoAdapter định gọi phương thức Adaptee.precise() nhưng không thực hiện được do khác danh sách tham số. Adapter giải quyết vấn đề này: phương thức Adapter.estimate() có signature phù hợp với lời gọi của DemoAdapter, và khi thực thi nó chuyển tiếp lời gọi đến phương thức Adapteee.precise(). Adapter chỉ đóng vai trò chuyển đổi giao diện, nó ủy nhiệm cho Adaptee thực hiện lời gọi.
2. Liên quan:
– Bridge: có cấu trúc tương tự, nhưng mục tiêu khác (tách một giao diện khỏi phần cài đặt).
– Decorator: bổ sung thêm chức năng nhưng không làm thay đổi giao diện, trong mẫu thiết kế Decorator, một Adapter sẽ phối hợp hai đối tượng khác nhau.
– Proxy: Định nghĩa một giao diện đại diện cho các đối tượng khác mà không làm thay đổi giao diện của các đối tượng được đại diện, điều này thực hiện được nhờ các Adapter.
3. Java API
Mẫu thiết kế Adapter được dùng phổ biến trong Java AWT (WindowAdapter, ComponentAdapter, ContainerAdapter, FocusAdapter, KeyAdapter, MouseAdapter và MouseMotionAdapter), …
Ví dụ: giao diện WindowListener có 7 phương thức. Khi listener của ta cài đặt giao diện này, cần phải cài đặt tất cả 7 phương thức xử lý sự kiệ, dù một số phương thức chỉ cài đặt rỗng do không dùng đến loại sự kiện đó. Lớp WindowAdapter cài đặt giao diện WindowListener, cài đặt sẵn và rỗng cả 7 phương thức. Như vậy nếu lớp listener của ta thừa kế từ lớp WindowAdapter, chỉ cần override những phương thức quan tâm.
4. Sử dụng
Ta muốn:
- Sử dụng một lớp đã tồn tại trước đó nhưng giao diện sử dụng không phù hợp như mong muốn, ta lại không có mã nguồn để sửa đổi giao diện đó.
- Sử dụng một lớp, nhưng lớp này được tạo ra với mục đích sử dụng chung, nên không phù hợp cho việc tạo một giao diện đặc thù.
- Có sự chuyển đổi giao diện từ nhiều nguồn khác nhau.
- Tiếp hợp nhiều đối tượng cùng một lúc, nhưng giao diện mong muốn không phải là interface mà là một lớp trừu tượng.