GoLang設計模式21 - 裝飾模式

robin·張發表於2022-03-13

裝飾器模式是一種結構型設計模式。通過裝飾器模式可以為一個物件新增額外的功能而不需對其作出調整。

還是通過具體的案例來了解裝飾器模式:假設我們開了一家披薩店,現在店裡主營兩款披薩:

  • 素食狂披薩(Veggie Mania Pizza)
  • 活力豆腐披薩(Peppy Tofu pizza)

以上兩款披薩有不同的價格,為獲取價格需要定義這樣一個介面:

package main

type pizza interface {
	getPrice() int
}

然後需要這兩款披薩分別建立一個struct並實現getPrice()函式來返回價格。因為定義了getPrice()函式,因此這兩款披薩的struct可以視為實現了pizza介面。

現在又有了一些變化:我們為設計了一些特色配料,這些配料也是需要收費的。這樣我們需要修改下之前的pizza介面,通過裝飾器的形式將配料(topping)的資訊給加進去。當前已有的配料為:

  • 番茄醬(TomatoTopping)
  • 乳酪(CheeseTopping)

另外,我們也得注意,加了配料的披薩也是一種新的披薩,所以現在顧客有了更多的選擇:

  • 素食狂披薩 + 番茄醬
  • 素食狂披薩 + 乳酪
  • 不加任何配料的素食狂披薩
  • 活力豆腐披薩 + 番茄醬
  • ...

加上配料的資訊後情況變得複雜起來了,為每種選擇都建立一個新的struct明顯是不可行的。裝飾器模式是一個不錯的解決方案:使用裝飾器模式可以在不修改已有的struct的前提下新增額外的功能。要使用裝飾器模式,我們需要為每種配料(Topping)分別建立一個struct。配料的struct也需要繼承前面的pizza介面並嵌入一個pizza介面的例項。

現在每種披薩以及每種配料都有一個獨立的struct了。每種披薩和配料都有各自的價格。當為披薩新增配料的時候,只需要在披薩的價格的基礎上加上配料的價格就可以計算出最終的價格。

現在可以看到裝飾器模式的作用了:我們不需要對pizza struct做任何調整,只是在pizza物件的基礎上做了一些裝飾就得到了最終的價格。在這個過程中pizza struct不知道 topping struct的任何資訊,只知道自己的價格。

下面是裝飾器模型的UML類圖:

 

類圖中ConcreteComponent(VeggieMania和PeppyTofu)和ConcreteDecorator(Topping)都實現了Component介面(Pizza),並且ConcreteDecorator還嵌入了一個Component介面的一個例項。

對比我們前面的例子:

  • pizza介面是圖中的Component
  • veggieManiapeppyPanner是圖中的ConcreteComponent,他們都實現了pizza介面
  • ConcreteDecorator的代表是cheeseToppingtomatoTopping,它們也都實現了pizza介面,同時它們也都嵌入了一個pizza介面的例項

來看看具體的程式碼吧:

pizza.go

type pizza interface {
	getPrice() int
}

peppyTofu.go

type peppyTofu struct {
}

func (p *peppyTofu) getPrice() int {
	return 20
}

veggeMania.go

type veggieMania struct {
}

func (p *veggieMania) getPrice() int {
	return 15
}

cheeseTopping.go

type cheeseTopping struct {
	pizza pizza
}

func (c *cheeseTopping) getPrice() int {
	pizzaPrice := c.pizza.getPrice()
	return pizzaPrice + 10
}

tomatoTopping.go

type tomatoTopping struct {
	pizza pizza
}

func (c *tomatoTopping) getPrice() int {
	pizzaPrice := c.pizza.getPrice()
	return pizzaPrice + 7
}

main.go

func main() {

	veggiePizza := &veggieMania{}

	//Add cheese topping
	veggiePizzaWithCheese := &cheeseTopping{
		pizza: veggiePizza,
	}

	//Add tomato topping
	veggiePizzaWithCheeseAndTomato := &tomatoTopping{
		pizza: veggiePizzaWithCheese,
	}

	fmt.Printf("Price of veggieMania pizza with tomato and cheese topping is %d\n", veggiePizzaWithCheeseAndTomato.getPrice())

	peppyTofuPizza := &peppyTofu{}

	//Add cheese topping
	peppyTofuPizzaWithCheese := &cheeseTopping{
		pizza: peppyTofuPizza,
	}

	fmt.Printf("Price of peppyTofu with tomato and cheese topping is %d\n", peppyTofuPizzaWithCheese.getPrice())

}

輸出內容:

Price of veggieMania pizza with tomato and cheese topping is 32
Price of peppyTofu with tomato and cheese topping is 30

程式碼已上傳至GitHub: zhyea / go-patterns / decorator-pattern

END!!!

相關文章