99 Ngày Java — Ngày 95: Decorator và Adapter
Cả Decorator và Adapter đều là wrapper: một đối tượng bọc quanh đối tượng khác. Khác nhau ở mục đích. Decorator giữ nguyên interface và thêm hành vi. Adapter đổi interface để hai phía vốn không khớp làm việc được với nhau. Bạn đã dùng cả hai mỗi lần đọc file bằng java.io.

1. Decorator: cùng interface, thêm hành vi
Decorator cài đặt cùng interface với đối tượng nó bọc, chuyển lời gọi vào bên trong và làm thêm việc trước hoặc sau đó. Vì trông giống hệt đối tượng gốc, nó thay thế được ở mọi nơi và có thể chồng nhiều lớp. Đây là cách mở rộng đúng tinh thần Open/Closed: lớp gốc không bị sửa, không cần tạo lớp con cho mọi tổ hợp tính năng.
interface Notifier { void send(String to, String message); }
final class RetryingNotifier implements Notifier {
private final Notifier delegate;
private final int attempts;
RetryingNotifier(Notifier delegate, int attempts) {
this.delegate = delegate;
this.attempts = attempts;
}
@Override
public void send(String to, String message) {
for (int i = 1; ; i++) {
try { delegate.send(to, message); return; }
catch (RuntimeException e) { if (i == attempts) throw e; }
}
}
}
Notifier notifier = new LoggingNotifier(new RetryingNotifier(new SmsNotifier(), 3));2. Wrapper trong JDK
java.io được xây gần như hoàn toàn trên Decorator. FileInputStream đọc byte từ file; BufferedInputStream bọc nó để thêm bộ đệm; GZIPInputStream bọc tiếp để giải nén. Mỗi lớp đều là InputStream nên ghép theo thứ tự nào cũng biên dịch được, và đóng lớp ngoài cùng sẽ đóng cả chuỗi. Collections.unmodifiableList và synchronizedList cũng là decorator của List.
try (var in = new GZIPInputStream(
new BufferedInputStream(
new FileInputStream("orders.csv.gz")));
var reader = new BufferedReader(
new InputStreamReader(in, StandardCharsets.UTF_8))) { // adapter
reader.lines().forEach(this::importLine);
}3. Adapter: đổi interface cho khớp
Adapter đứng giữa code của bạn và một thứ có interface khác: thư viện bên thứ ba, hệ thống cũ, hoặc một kiểu của JDK. Nó cài đặt interface mà phía bạn mong đợi và dịch lời gọi sang interface của phía kia. Trong ví dụ trên, InputStreamReader là adapter từ dòng byte sang dòng ký tự. Lợi ích lớn nhất là cô lập: khi đổi nhà cung cấp, bạn viết adapter mới thay vì sửa logic nghiệp vụ.
interface PaymentGateway { Receipt charge(String orderId, Money amount); }
final class AcmePayAdapter implements PaymentGateway {
private final AcmePayClient client; // vendor SDK, different shape
AcmePayAdapter(AcmePayClient client) { this.client = client; }
@Override
public Receipt charge(String orderId, Money amount) {
var response = client.createCharge(
new AcmeChargeRequest(orderId, amount.cents(), "VND"));
return new Receipt(response.reference(), response.succeeded());
}
}4. Phân biệt và dùng đúng chỗ
Hỏi một câu là phân biệt được: interface ở hai phía của wrapper có giống nhau không. Giống thì là Decorator, mục đích là thêm hành vi. Khác thì là Adapter, mục đích là chuyển đổi. Cả hai đều dùng composition thay cho kế thừa, nên không bị ràng buộc vào lớp cụ thể và test được bằng một đối tượng giả bên trong.
Decorator hợp cho mối quan tâm cắt ngang: log, retry, cache, đo thời gian.
Thứ tự chồng decorator có ý nghĩa: log bên ngoài retry sẽ ghi một lần, bên trong sẽ ghi mỗi lần thử.
Adapter chỉ chuyển đổi; đừng nhét quy tắc nghiệp vụ vào đó.
Trong Spring, AOP và các interceptor là decorator được tạo tự động.
Bọc thay vì sửa là ý chung của hai mẫu này. Ngày 96 xem công cụ đứng sau rất nhiều framework: reflection và annotation.
Tài liệu đối chiếu
https://refactoring.guru/design-patterns/decorator
https://refactoring.guru/design-patterns/adapter
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


