Trong các bài viết trước, chúng ta đã tìm hiểu về các Pattern thuộc nhóm Creational Design Pattern. Trong bài viết này, chúng ta sẽ đi vào một Pattern khác thuộc nhóm Structural Design Pattern là Adapter Pattern.
Adapter Pattern là gì?
Adapter Pattern là một trong những Pattern thuộc nhóm Structural Pattern. Nó cho phép các interface không liên quan tới nhau có thể làm việc cùng nhau. Đối tượng giúp kết nối các interface gọi là Adapter.
Adapter Pattern giữ vai trò trung gian giữa hai lớp, chuyển đổi interface của một hay nhiều lớp có sẵn thành một interface khác, thích hợp cho lớp đang viết. Điều này cho phép các lớp có các interface khác nhau có thể dễ dàng giao tiếp tốt với nhau thông qua interface trung gian, không cần thay đổi code của lớp có sẵn cũng như lớp đang viết.
Adapter Pattern còn được gọi là Wrapper Pattern do cung cấp một interface “bọc ngoài” tương thích cho một hệ thống có sẵn, có dữ liệu và hành vi phù hợp nhưng có interface không tương thích với lớp đang viết.
Ví dụ:
- Cái phích cắm điện có 3 chân nhưng ổ điện chỉ có 2 lỗ thì phải dùng thêm 1 cái bộ chuyển để chuyển từ 3 chân sang 2 chân - bộ chuyển này cũng được gọi là Adapter.
- Một ví dụ khác là laptop không sử dụng nguồn điện xoay chiều 224V, nên để laptop có thể sử dụng được nguồn điện 224V cần có một adapter làm cầu nối trung gian để chuyển nguồn điện xoay chiều 224V thành nguồn điện 1 chiều 12V.
Cài đặt Adapter Pattern như thế nào?
Một Adapter Pattern bao gồm các thành phần cơ bản sau:
- 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 chức năng được sử dụng bởi Client (domain specific).
- Client: lớp sử dụng các đối tượng có interface Target.
Có hai cách để thực hiện Adapter Pattern dựa theo cách cài đặt (implement) của chúng:
- Object Adapter - Composition (Chứa trong): 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.
- Class Adapter - Inheritance (Kế thừa) : 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.
So sánh Class Adapter với Object Adapter:
- Sự khác biệt chính là Class Adapter sử dụng Inheritance (kế thừa) để kết nối Adapter và Adaptee trong khi Object Adapter sử dụng Composition (chứa trong) để kết nối Adapter và Adaptee.
- Trong cách tiếp cận Class Adapter, nếu một Adaptee là một class và không phải là một interface thì Adapter sẽ là một lớp con của Adaptee. Do đó, nó sẽ không phục vụ tất cả các lớp con khác theo cùng một cách vì Adapter là một lớp phụ cụ thể của Adaptee.
Tại sao Object Adapter lại tốt hơn?
- Nó 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.
Ví dụ Adapter Pattern với ứng dụng Translation
Một người Việt muốn trao đổi với một người Nhật. Tuy nhiên, 2 người này không biết ngôn ngữ của nhau nên cần phải có một người để chuyển đổi từ ngôn ngữ tiếng Việt sang ngôn ngữ tiếng Nhật. Chúng ta sẽ mô hình hóa trường hợp này với Adapter Pattern như sau:
- Client: người Việt sẽ là Client trong ví dụ này, vì anh ta cần gửi một số message cho người Nhật.
- Target: đây là nội dung message được Client cung cấp cho thông dịch viên (Translator / Adapter).
- Adapter: thông dịch viên (Translator) sẽ là Adapter, nhận message tiếng Việt từ Client và chuyển đổi nó sang tiếng Nhật trước khi gởi cho người Nhật.
- Adaptee: đây là interface hoặc class được người Nhật sử dụng để nhận message được chuyển đổi từ thông dịch viên (Translator).
// VietnameseTarget.java
package com.gpcoder.patterns.structural.adapter;
public interface VietnameseTarget {
void send(String words);
}
// JapaneseAdaptee.java
package com.gpcoder.patterns.structural.adapter;
public class JapaneseAdaptee {
public void receive(String words) {
System.out.println("Retrieving words from Adapter ...");
System.out.println(words);
}
}
// TranslatorAdapter.java
package com.gpcoder.patterns.structural.adapter;
public class TranslatorAdapter implements VietnameseTarget {
private JapaneseAdaptee adaptee;
public TranslatorAdapter(JapaneseAdaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void send(String words) {
System.out.println("Reading Words ...");
System.out.println(words);
String vietnameseWords = this.translate(words);
System.out.println("Sending Words ...");
adaptee.receive(vietnameseWords);
}
private String translate(String vietnameseWords) {
System.out.println("Translated!");
return "こんにちは";
}
}
// VietnameseClient.java
package com.gpcoder.patterns.structural.adapter;
public class VietnameseClient {
public static void main(String[] args) {
VietnameseTarget client = new TranslatorAdapter(new JapaneseAdaptee());
client.send("Xin chào");
}
}
// Output của chương trình trên:
// Reading Words ... Xin chào
// Translated!
// Sending Words ...
// Retrieving words from Adapter ...
// こんにちは
Ví dụ Adapter Pattern với BufferedReader
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Enter your name: ");
String s = br.readLine();
Như bạn thấy:
- System.in: đây là một Adaptee. System.in là một static instance của lớp InputStream, nó đọc dữ liệu từ Console và trả về 1 byte stream.
- BufferedReader : đây là Target, nó chấp nhận dữ liệu là một character stream.
- InputStreamReader : đây là một Adapter ở giữa hai interface không tương thích: System.in và BufferedReader giúp cho chúng có thể hoạt động được với nhau.
- Client: là ứng dụng sẽ làm việc với Target interface.
Lợi ích của Adapter Pattern là gì?
Việc sử dụng Adapter Pattern đem lại các lợi ích sau:
- Giúp kết nối các interface không tương thích với nhau.
- Đạt được tính linh hoạt và sử dụng lại code hiệu quả.
- Giảm sự phụ thuộc giữa các lớp.
- Dễ dàng để bảo trì và mở rộng.
Kết luận
Adapter Pattern là một Pattern quan trọng thuộc nhóm Structural Pattern. Nó giúp kết nối các interface không tương thích với nhau một cách linh hoạt và hiệu quả. Việc sử dụng Adapter Pattern giúp tăng tính linh hoạt, tái sử dụng và dễ dàng bảo trì của code.