Trong bài viết này chúng ta sẽ cùng tìm hiểu về decorator design pattern bằng ngôn ngữ scala
Mục đích
- Gắn các trách nhiệm bổ sung cho một object động. Decorator cung cấp một thay thế linh hoạt cho sub-class cho chức năng mở rộng.
- Thêm vào client được chỉ định của core object bằng cách đệ quy đóng gói nó
- Đóng gói quà, đặt vào trong một cái hộp và đóng gói hộp đó
Cấu trúc
Client thường chỉ quan tâm đến CoreFunctionality.doThis(). Client có thể hoặc không quan tâm đến OptionalOne.doThis()
và OptionalTwo.doThis()
. Mỗi class này luôn luôn đại diện cho base class Decorator và class đó luôn đại diện cho object “wrappee” chứa
Decorator design pattern là gì?
Decorator design pattern là một structural design pattern
Structural design pattern tập trung vào các thành phần class và object.
Pattern này là về tạo một decorator class mà có thể đóng gói class ban đầu và cung cấp thêm các chức năng giữ cho các phương thức class đăng ký nguyên vẹn
Một thiết kế sử dụng Decorator thường đưa ra kết quả trong hệ thống bao gồm nhiều các object nhỏ mà tất cả trông giống nhau
Ví dụ
Giả sử chúng ta có một cửa hàng Pizza và chúng ta đều hiểu rằng khách hàng sẽ có những khẩu vị khác nhau, do đó có thể cần kết hợp phần khác nhau trên lớp bề mặt.
Nếu chúng ta có n phần bề mặt pizza, thì chúng ta phải tạo p(n) = 2*p(n-1) sub-class.
p(0) = 0
p(1) = 2 * p(1-1) + 1 = 1
p(2) = 2 * p(2-1) + 1 = 2 * p(1) + 1 = 2 * 1 + 1 = 3
p(3) = 2 * p2 + 1 = 2 * 3 + 1 = 7
p(4) = 2 * p3 + 1 = 2 * 7 + 1 = 15
Nếu chúng ta có 3 bề mặt thì số sub-class sẽ là p(3) = 7
Số lượng khách hàng ngày càng đông nên tôi muốn mở rộng nó. Nên tôi sẽ thêm 2 lựa chọn bề mặt nữa cho các khách hàng quan trọng. Nên bây giờ chúng ta có 5 bề mặt và số sub-class sẽ là 31.
Nhưng đây đúng là một công việc nhàn chán mà chúng ta phải làm. Nên chúng ta sẽ thử sử dụng decorator design pattern để giải quyết vấn đề này.
Đầu tiên chúng ta sẽ tạo một Topping trait mà được implement bởi BasePizza và ToppingDecorator class. Pizza class tổng hợp nó.
ToppingDecorator được extend bởi CheeseTopping và OnionTopping
BasePizza.scala
class BasePizza extends Topping { def getName() : String = "Pizza" def getPrice() : Double = 77.0 def addTopping() : Topping = this }
CheeseTopping.scala
class CheeseTopping(override val topping : Topping) extends ToppingDecorator(topping) { override def getPrice() : Double = { super.getPrice() + 59.0 } override def getName() : String = { val previous = super.getName() "Ocean Cheese " + previous } }
OnionTopping.scala
class OnionTopping(override val topping : Topping) extends ToppingDecorator(topping) { override def getPrice() : Double = { super.getPrice() + 39.0 } override def getName() : String = { val previous = super.getName() "Sprinkled Onion " + previous } }
Pizza.scala
class Pizza { var topping : Topping = new BasePizza def getPrice() : Double = { topping.getPrice() } def getName() : String = { topping.getName() } def addNewTopping(toppingName : String) : Boolean = { toppingName match { case "Onion" => { this.topping = new OnionTopping(topping) true } case "Cheese" => { this.topping = new CheeseTopping(topping) true } case _ => println("Topping unavailable! Better luck next time! :(") false } } }
PizzaStore.scala
object PizzaStore extends App { val pizza = new Pizza pizza.addNewTopping("Cheese") pizza.addNewTopping("Onion") println(s"You have ordered ${pizza.getName}") println(s"You have to pay Rupees ${pizza.getPrice}") }
Topping.scala
trait Topping { def getName() : String def getPrice() : Double def addTopping() : Topping }
ToppingDecorator.scala
class ToppingDecorator(val topping : Topping) extends Topping { var nextTopping : Topping = topping def getName() : String = nextTopping.getName() def getPrice() : Double = nextTopping.getPrice() def addTopping() : Topping = this }