Backend

99 Ngày Java — Ngày 95: Decorator và Adapter

SSite Admin
30 tháng 10, 2026 3 phút đọc 6 lượt xem
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.

Decorator và Adapter

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

S

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.

Bài viết liên quan

Kafka trong dự án thực tế: đồng bộ PostgreSQL sang Elasticsearch bằng Debezium CDC

Thiết kế pipeline CDC cho catalog sản phẩm: Kafka Connect, snapshot, update/delete, WAL, retry, retention và dựng lại Elasticsearch bằng index mới cùng alias — kèm cấu hình và sơ đồ thực tế.

30 thg 10, 202633 phút1
99 Ngày Spring — Ngày 95: Deploy lên Kubernetes

Deployment, Service và Ingress, probe từ Actuator, ConfigMap và Secret, cùng graceful shutdown để rolling update không rớt request.

30 thg 10, 20263 phút7
99 Ngày Spring — Ngày 94: CI/CD cho Spring Boot

Workflow GitHub Actions để build, test và đóng image, đánh phiên bản bất biến và triển khai qua nhiều môi trường có kiểm soát.

29 thg 10, 20263 phút7