Decorator Pattern là một trong những mẫu thiết kế thuộc nhóm Structural Patterns (mẫu cấu trúc). Mục tiêu của mẫu này là cho phép thêm các chức năng mới vào một đối tượng một cách linh hoạt mà không cần thay đổi cấu trúc của đối tượng đó. Thay vì kế thừa từ lớp cha để tạo ra các lớp con với các chức năng mở rộng, Decorator Pattern sử dụng một lớp trang trí (decorator) để “gói” đối tượng ban đầu và bổ sung thêm các chức năng.
Mục đích của Decorator Pattern
Mẫu Decorator cho phép bạn:
- Thêm chức năng mới vào một đối tượng mà không ảnh hưởng đến các đối tượng khác của cùng một lớp.
- Dễ dàng kết hợp nhiều chức năng khác nhau cho một đối tượng mà không cần tạo ra nhiều lớp con.
- Cải thiện khả năng mở rộng của hệ thống mà không làm phức tạp hóa cấu trúc lớp.
Cách hoạt động của Decorator Pattern
Decorator Pattern hoạt động bằng cách tạo ra một lớp trang trí (decorator) có cùng giao diện với đối tượng mà nó trang trí. Lớp này sẽ giữ một tham chiếu đến đối tượng gốc và bổ sung các chức năng mới cho đối tượng đó.
Ví dụ
Giả sử chúng ta đang xây dựng một ứng dụng cà phê và muốn cho phép người dùng thêm các thành phần như sữa, đường vào cà phê. Chúng ta có thể sử dụng Decorator Pattern để bổ sung các thành phần này mà không cần thay đổi lớp gốc của cà phê.
Giao diện Cà phê
public interface Coffee {
String getDescription();
double cost();
}
Lớp Cà phê cơ bản
public class BasicCoffee implements Coffee {
@Override
public String getDescription() {
return "Basic Coffee";
}
@Override
public double cost() {
return 2.0; // Giá của cà phê cơ bản
}
}
Lớp Decorator
public abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription(); // Gọi mô tả từ đối tượng cà phê
}
@Override
public double cost() {
return coffee.cost(); // Gọi giá từ đối tượng cà phê
}
}
Các thành phần trang trí (decorator)
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Milk"; // Thêm mô tả cho sữa
}
@Override
public double cost() {
return coffee.cost() + 0.5; // Thêm giá cho sữa
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Sugar"; // Thêm mô tả cho đường
}
@Override
public double cost() {
return coffee.cost() + 0.2; // Thêm giá cho đường
}
}
Sử dụng Decorator Pattern
public class Main {
public static void main(String[] args) {
Coffee myCoffee = new BasicCoffee(); // Tạo một cốc cà phê cơ bản
// Thêm sữa
myCoffee = new MilkDecorator(myCoffee);
// Thêm đường
myCoffee = new SugarDecorator(myCoffee);
System.out.println(myCoffee.getDescription()); // In ra mô tả
System.out.println("Total Cost: " + myCoffee.cost()); // In ra tổng giá
}
}
Output:
Basic Coffee, Milk, Sugar
Total Cost: 2.7
Khi nào nên sử dụng Decorator Pattern?
- Khi bạn muốn mở rộng chức năng của một đối tượng mà không thay đổi lớp gốc.
- Khi bạn cần tạo ra nhiều sự kết hợp khác nhau của các chức năng mà không cần tạo ra nhiều lớp con.
- Khi bạn muốn tránh việc sử dụng kế thừa sâu sắc (deep inheritance) và giữ mã nguồn gọn gàng.
Lợi ích của Decorator Pattern
- Tăng tính linh hoạt: Bạn có thể thêm và gỡ bỏ các trang trí (decorator) tại thời điểm chạy, cho phép linh hoạt trong việc quản lý chức năng.
- Tránh lớp con lớn: Bạn không cần phải tạo ra nhiều lớp con cho từng sự kết hợp chức năng, giảm độ phức tạp trong hệ thống.
- Dễ bảo trì: Mỗi lớp trang trí có thể được phát triển và bảo trì một cách độc lập, giúp dễ dàng điều chỉnh hoặc thêm chức năng mới mà không ảnh hưởng đến các lớp khác.
Kết luận
Decorator Pattern là một công cụ mạnh mẽ giúp bạn thêm các chức năng mới vào đối tượng mà không cần thay đổi cấu trúc của lớp. Mẫu này giúp tăng cường tính linh hoạt, khả năng tái sử dụng mã và dễ bảo trì trong thiết kế phần mềm. Việc sử dụng Decorator Pattern có thể giúp đơn giản hóa việc quản lý và mở rộng các chức năng của đối tượng trong hệ thống.