Menu
CoddyTech

Mẫu Decorator

Thuộc phần Lập trình hướng đối tượng trong hành trình Java của Coddy. Bài học 72 trên 87.

Mẫu Decorator là một mẫu thiết kế cấu trúc cho phép bạn gắn thêm hành vi mới vào các đối tượng bằng cách bọc chúng trong những đối tượng đặc biệt gọi là decorator. Khác với kế thừa, vốn bổ sung hành vi tại thời điểm biên dịch, decorator bổ sung chức năng một cách linh hoạt trong lúc chạy mà không sửa đổi lớp ban đầu.

Hãy tưởng tượng một quán cà phê, nơi bạn bắt đầu với một ly cà phê cơ bản rồi có thể thêm các món như sữa, đường hoặc kem tươi. Mỗi món thêm vào sẽ bao bọc ly cà phê ban đầu, đồng thời cộng thêm chi phí và phần mô tả. Mẫu thiết kế này dùng một giao diện chung để có thể xử lý các đối tượng đã trang trí giống như đối tượng ban đầu:

interface Coffee {
    String getDescription();
    double getCost();
}

class SimpleCoffee implements Coffee {
    public String getDescription() {
        return "Simple Coffee";
    }
    public double getCost() {
        return 2.0;
    }
}

abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    
    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
}

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }
    
    public String getDescription() {
        return coffee.getDescription() + ", Milk";
    }
    
    public double getCost() {
        return coffee.getCost() + 0.5;
    }
}

Decorator giữ tham chiếu đến đối tượng được bọc và chuyển tiếp các lệnh gọi đến đối tượng đó, đồng thời bổ sung hành vi riêng. Bạn có thể xếp chồng nhiều decorator để kết hợp các tính năng:

Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);

System.out.println(order.getDescription());  // Simple Coffee, Milk, Sugar
System.out.println(order.getCost());         // 2.75

Mẫu Decorator phát huy hiệu quả khi bạn cần bổ sung trách nhiệm cho các đối tượng mà không tạo ra quá nhiều lớp con. Mỗi decorator hoạt động độc lập, vì vậy bạn có thể tự do kết hợp chúng để tạo ra chính xác tổ hợp hành vi mình cần.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống thông báo bằng mẫu thiết kế Decorator! Bạn sẽ tạo một thông báo cơ sở có thể được mở rộng bằng nhiều kênh gửi khác nhau, gồm cảnh báo qua email, cảnh báo SMS và thông báo đẩy, bằng cách lồng các decorator vào nhau. Cách này phản ánh những hệ thống thực tế, nơi một thông điệp duy nhất có thể cần được gửi đồng thời qua nhiều kênh.

Bạn sẽ sắp xếp code trong bốn tệp:

  • Notifier.java: Định nghĩa giao diện Notifier, đóng vai trò là giao diện thành phần cho mẫu thiết kế decorator. Giao diện này cần khai báo phương thức send(String message) để xử lý việc gửi thông báo.
  • BasicNotifier.java: Tạo lớp thành phần cụ thể triển khai Notifier. Lớp này đại diện cho hình thức thông báo đơn giản nhất: cảnh báo trong ứng dụng. Khi gọi send, lớp sẽ in ra In-App: [message].
  • NotifierDecorators.java: Xây dựng hệ thống phân cấp decorator tại đây. Bắt đầu bằng lớp trừu tượng NotifierDecorator, lớp này triển khai Notifier và giữ tham chiếu đến một đối tượng Notifier được bọc (truyền vào qua hàm khởi tạo).

    Sau đó, tạo ba decorator cụ thể kế thừa NotifierDecorator:

    EmailDecorator - Khi gọi send, trước tiên lớp này chuyển tiếp lời gọi đến notifier được bọc, sau đó in ra Email: [message].

    SMSDecorator - Khi gọi send, trước tiên lớp này chuyển tiếp lời gọi đến notifier được bọc, sau đó in ra SMS: [message].

    PushDecorator - Khi gọi send, trước tiên lớp này chuyển tiếp lời gọi đến notifier được bọc, sau đó in ra Push: [message].

  • Main.java: Kết hợp hệ thống decorator của bạn! Bạn sẽ nhận hai đầu vào: thông điệp thông báo (String) và danh sách các kênh cần thêm, được phân tách bằng dấu phẩy (String), chẳng hạn như "email,sms" hoặc "push,email,sms".

    Bắt đầu với một BasicNotifier. Sau đó, dựa trên đầu vào về các kênh, bọc nó bằng các decorator phù hợp theo thứ tự xuất hiện. Tên các kênh hợp lệ là email, sms và push.

    Cuối cùng, gọi send với thông điệp của bạn trên notifier đã được bọc đầy đủ.

Bạn sẽ nhận hai đầu vào theo thứ tự: thông điệp cần gửi (String) và các kênh cần bật (String, phân tách bằng dấu phẩy).

Ví dụ, với đầu vào Server is down! và email,push, đầu ra sẽ là:

In-App: Server is down!
Email: Server is down!
Push: Server is down!

Lưu ý cách mỗi decorator thêm hành vi riêng mà vẫn giữ nguyên thông báo ban đầu. Thứ tự lồng các lớp quyết định thứ tự đầu ra: thành phần trong cùng (BasicNotifier) chạy trước, sau đó mỗi decorator thêm lớp xử lý của mình. Bạn có thể kết hợp tùy ý các kênh mà không cần tạo lớp riêng cho từng tổ hợp!

Tự làm thử

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Đọc thông điệp cần gửi
        String message = scanner.nextLine();
        
        // Đọc danh sách kênh phân tách bằng dấu phẩy (ví dụ: "email,sms" hoặc "push,email,sms")
        String channelsInput = scanner.nextLine();
        
        // TODO: Bắt đầu với BasicNotifier
        
        // TODO: Tách đầu vào channels theo dấu phẩy
        
        // TODO: Lặp qua từng kênh và bọc notifier bằng decorator phù hợp
        // Các kênh hợp lệ là: "email", "sms", "push"
        
        // TODO: Gọi send trên notifier đã được bọc đầy đủ, truyền vào thông điệp
        
    }
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Java online