Đôi khi chúng ta cần mở rộng một phương thức trong đối tượng, và cách thông thường là chúng ta sẽ kế thừa đối tượng đó. Việc này không phải sai, nhưng trong một vài trường hợp sẽ làm cho mã nguồn trở lên phức tạp hơn chúng ta mong muốn. Đó là lý do chính cho việc ra đời của mẫu thiết kế Decorator, là một cách để mở rộng các phương thức một cách linh động.
Theo như GoF (Gang of Four), mẫu thiết kế Decorator là một trong nhiều những mẫu thiết kế cấu trúc thường được sử dụng. Mẫu thiết kế này sẽ linh động thay đổi tính chất (functionality) đã có trong một đối tượng khi chương trình đang chạy (runtime) mà không ảnh hưởng đến các tình chất đã tồn tại của các đối tượng khác.
Vậy điều gì là điểm khác biệt giữa mở rộng phương thức theo cách linh động với mở rộng phương thức theo cách tĩnh? Về cơ bản, để mở rộng phương thức của đối tượng bằng cách kế thửa chúc ta cần phải triển khai code để mở rộng lớp có sẵn và viết lại (overide) các thương thức của lớp đó. Và những mở rộng đó sẽ được cài đặt ngay khi biên dịch chương trình. Trong một hình thức khác, chúng ta sẽ tạo ra một lớp mới đồng thời chúng ta sẽ áp dụng sự thay đổi trên tất các đối tượng thuộc lớp này. Mở rộng theo cách linh động là chúng ta sẽ cung cấp một cơ cấu mà cơ cấu này cho phép chúng ta thay đổi một đối tượng đã tồn tại nhưng không làm ảnh hưởng đến các đối tượng khác của cùng lớp đó.
Thêm nữa, có nhiều trường hợp mà việc sử dụng kế thừa sẽ mất nhiều công sức trong việc viết code. Mặt khác, mẫu thiết kế Decorator sẽ giảm cho bạn một lượng lớn thời gian viết code. Hãy xem xem xét một ví dụ rất phổ biến trong những cuốn sách về GoF, đó là ví dụ về Pizza. Tưởng tượng về một kịch bản rằng bạn đang làm việc cho một cửa hàng bánh Pizza, và cửa hàng của bạn vừa làm pizza cà chua và pizza phô mai. Sau đó bạn cần đặt thêm một vài nguyên liệu nữa lên phần trên của bánh vì khách hàng có quyền lựa chọn thêm gà hoặc hồ tiêu và chiếc bánh của họ. Về cơ bản bạn có một số loại pizza như: pizza gà cà chua, pizza cà chua hạt tiêu, pizza gà phô mai, pizza phô mai hồ tiêu, pizza cà chua gà hồ tiêu và phô mai gà hồ tiêu. Trong trường hợp này, nếu chúng ta giải quyết vấn đề này với cách mở rộng tĩnh thì bạn sẽ cần tạo ra một số lượng lớn các lớp như TomatoChickenPizza, TomatoPepperPizza … nó có nghĩa đây sẽ là một số lượng lớn của các kết hợp được thêm vào vào để có được cái mà khách hàng mong muốn. Và sẽ có nhiều lớp mà bạn cần phải cài đặt. Mặt khác, mấu thiết kế Decorator sẽ giúp bạn giảm đi một lượng lớn các lớp được mở rộng. Để rõ hơn về điều này, hãy xem biểu đồ lớp và cách thức hoạt động của mẫu thiết kế Decorator dưới đây:
Những thành phần trong mẫu thiết kế Decorator:
- Component: giao diện (interface) chung để các đối tượng cần thêm chức năng trong quá trình chạy thì triển khai giao diện này.
- ConcreteComponent : Một cài đặt cho giao diện Component mà nó định nghĩa một đối tượng cần thêm các chức năng trong quá trình chạy.
- Decorator : một lớp trừu tượng dùng để duy trì một tham chiếu của đối tượng thành phần và đồng thời cài đặt các thành phần của giao diện.
- ConcreteDecorator : Một cài đặt của Decorator, nó cài đặt thêm các thành phần vào đầu của các đối tượng thành phần.
Bây giờ quay lại với ví dụ Pizza đã nêu ở bên trên, chúng ta sẽ triển khai hệ thống như hình bên dưới:
Giao diện IPizza là thành phần Component trong mẫu thiết kế Decorator, nó chứa phương thức doPizza, đây là phương thức dùng để tạo ra một pizza phù hợp.
/** * This is our component interface * It has doPizza() method that provides a Pizza cake. * Created by TienDQ on 1/28/16. */ public interface IPizza { String doPizza();
}
TomatoPizza và ChickenPizza là những cài đặt, triển khai của IPizza. Chúng cung cấp cụ thể các thể hiện của lớp mà chúng ta cẩn mở rộng trong quá trình chương trình đang chạy.
/** * Created by TienDQ on 1/28/16. */
public class TomatoPizza implements IPizza { @Override public String doPizza() { return "I am a Tomato Pizza"; }
}
/** * Created by TienDQ on 1/28/16. */
public class ChickenPizza implements IPizza { @Override public String doPizza() { return "I am a Chicken Pizza"; }
}
PizzaDecorator là trái tim của sơ đồ thiết kế trên. Nó giữ một thể hiện đã tồn tại của pizza như TomatoPizza hoặc ChickenPizza. Thuộc tính này sẽ được cài đặt thông qua phương thức cởi tạo, và nó được mở rộng trong khi chương trình chạy.
/** * This is our abstract Decorator class It maintains a reference to IPizza * instance that is object need additional functionalities at runtime. * * Created by TienDQ on 1/28/16. */ public abstract class PizzaDecorator implements IPizza { // Reference to component object protected IPizza mPizza; // We initialize a Decorator with existing pizza we need decorate public PizzaDecorator(IPizza pizza) { mPizza = pizza; } public IPizza getPizza() { return mPizza; } public void setPizza(IPizza mPizza) { this.mPizza = mPizza; }
}
PepperDecorator và CheeseDecorator cài đặt các phương thức mở rộng, trong trường hợp ví dụ này, PepperDecorator sẽ thêm hồ tiêu vào một pizza đã có. Tính năng mở rộng là được cài đặt trong phương thức addPepper().
/** * This is class that extends an Pizza by adding cheese at runtime. * Created by TienDQ on 1/28/16. */
public class CheeseDecorator extends PizzaDecorator { public CheeseDecorator(IPizza pizza) { super(pizza); } @Override public String doPizza() { String type = mPizza.doPizza(); return type + addCheese(); } // This is additional functionality // It adds cheese to an existing pizza private String addCheese() { return "+ Cheese"; }
}
/** * This class extends an Pizza by adding pepper to an existing one. * * Created by TienDQ on 1/28/16. */
public class PepperDecorator extends PizzaDecorator { public PepperDecorator(IPizza pizza) { super(pizza); } @Override public String doPizza() { String type = mPizza.doPizza(); return type + addPepper(); } // This is our additional functionality // It add pepper to existing pizza at runtime private String addPepper() { return "+ Pepper"; }
}
Và cuối cùng chúng ta cần viết lớp để chạy các cài đặt ở bên trên là lớp PizzaShop
/** * Created by TienDQ on 1/28/16. */
public class PizzaShop { public static void main(String[] args) { IPizza tomato = new TomatoPizza(); IPizza chicken = new ChickenPizza(); System.out.println(tomato.doPizza()); System.out.println(chicken.doPizza()); // Use Decorator pattern to extend existing pizza dynamically // Add pepper to tomato-pizza PepperDecorator pepperDecorator = new PepperDecorator(tomato); System.out.println(pepperDecorator.doPizza()); // Add cheese to tomato-pizza CheeseDecorator cheeseDecorator = new CheeseDecorator(tomato); System.out.println(cheeseDecorator.doPizza()); // Add cheese and pepper to tomato-pizza // We combine functionalities together easily. CheeseDecorator cheeseDecorator2 = new CheeseDecorator(pepperDecorator); System.out.println(cheeseDecorator2.doPizza()); }
}
Và kết quả chúng ta thu được khi chạy chương trình:
I am a Tomato Pizza
I am a Chicken Pizza
I am a Tomato Pizza+ Pepper
I am a Tomato Pizza+ Cheese
I am a Tomato Pizza+ Pepper+ Cheese
Đố là tất cả những gì cần biết về mẫu thiết kế Decorator. Trong thế giới lập trình, mẫu thiết kế này được sự dụng rất nhiều. Và đặc biệt là khi làm việc với stream trong Java.
Reference: