Trước tiên, chúng ta hãy cùng cafedev giới thiệu mọi thứ về Decorator Pattern và phần code ví dụ chi tiết nhằm giúp ace dễ hiểu khi áp dụng trên các ngôn ngữ khác nhau. Ace có thể tham khảo thêm các bài khác tại series Design Pattern tại đây.

Để hiểu decorator pattern qua ví dụ sau. Giả sử chúng ta đang xây dựng một ứng dụng cho một cửa hàng pizza và chúng ta cần lập mô hình các lớp bánh pizza của họ. Giả sử họ cung cấp bốn loại pizza là Peppy Paneer, Farmhouse, Margherita và Chicken Fiesta. Ban đầu, chúng ta chỉ sử dụng kế thừa và tóm tắt các chức năng phổ biến trong một lớp Pizza  .

Mỗi chiếc bánh pizza có giá thành khác nhau. Chúng ta đã ghi đè getCost() trong các lớp con để tìm chi phí thích hợp. Bây giờ, giả sử có một yêu cầu mới, ngoài bánh pizza, khách hàng cũng có thể yêu cầu một số lớp phủ như Fresh Tomato, Paneer, Jalapeno, Capsicum, Barbeque, v.v. Đôi khi, chúng ta hãy nghĩ xem chúng ta làm cách nào để đáp ứng những thay đổi trong các lớp trên để khách hàng có thể chọn bánh pizza với lớp phủ và chúng ta nhận được tổng chi phí của bánh pizza và lớp phủ mà khách hàng chọn.

Hãy để chúng ta xem xét các tùy chọn khác nhau.

Tùy chọn 1

Tạo một lớp con mới cho mỗi lớp phủ trên bánh pizza. Sơ đồ lớp sẽ giống như sau:

Điều này trông rất phức tạp. Có quá nhiều lớp và là một cơn ác mộng bảo trì. Ngoài ra, nếu chúng ta muốn thêm topping hoặc pizza mới, chúng ta phải thêm nhiều lớp như vậy. Đây rõ ràng là một thiết kế rất tệ.

Tùy chọn 2:

Hãy thêm các biến instance vào lớp đế bánh pizza để biểu thị việc mỗi chiếc bánh pizza có lớp phủ trên hay không. Sơ đồ lớp sẽ giống như sau:

GetCost () của lớp cha tính toán chi phí cho tất cả các lớp phủ trong khi lớp con trong lớp con thêm chi phí của chiếc bánh pizza cụ thể đó.

// Sample getCost() in super class
public int getCost()
{
    int totalToppingsCost = 0;
    if (hasJalapeno() )
        totalToppingsCost += jalapenoCost;
    if (hasCapsicum() )
        totalToppingsCost += capsicumCost;

    // similarly for other toppings
    return totalToppingsCost;
}
// Sample getCost() in subclass
public int getCost()
{
    // 100 for Margherita and super.getCost()
    // for toppings.
    return super.getCost() + 100;
}

Thiết kế này thoạt nhìn có vẻ tốt nhưng chúng ta hãy xem xét các vấn đề liên quan đến nó.

  • Thay đổi giá đối với lớp phủ sẽ dẫn đến sự thay đổi trong code hiện có.
  • Các lớp phủ mới sẽ buộc chúng ta phải thêm các phương thức mới và thay đổi phương thức getCost() trong lớp cha.
  • Đối với một số loại pizza, một số lớp phủ có thể không phù hợp nhưng lớp con kế thừa chúng.
  • Điều gì sẽ xảy ra nếu khách hàng muốn ớt chuông kép hoặc viên pho mát kép?

Tóm lại, thiết kế của chúng ta vi phạm một trong những nguyên tắc thiết kế phổ biến nhất – Nguyên tắc Mở-Đóng nói rằng các lớp phải mở để mở rộng và đóng để sửa đổi.

Trong phần tiếp theo, chúng ta sẽ giới thiệu Decorator Pattern và áp dụng nó cho vấn đề trên.

Cài ứng dụng cafedev để dễ dàng cập nhật tin và học lập trình mọi lúc mọi nơi tại đây.

Tài liệu từ cafedev:

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!