GoLang设计模式23 - 装饰模式(结构型)

装饰器模式是一种结构型设计模式。通过装饰器模式可以为一个对象添加额外的功能而不需对其作出调整。

还是通过具体的案例来了解装饰器模式:假设我们开了一家披萨店,现在店里主营两款披萨:

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

以上两款披萨有不同的价格,为获取价格需要定义这样一个接口:

1 package main
2  
3 type pizza interface {
4     getPrice() int
5 }

然后需要这两款披萨分别创建一个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

1 type pizza interface {
2     getPrice() int
3 }

peppyTofu.go

1 type peppyTofu struct {
2 }
3  
4 func (p *peppyTofu) getPrice() int {
5     return 20
6 }

veggeMania.go

1 type veggieMania struct {
2 }
3  
4 func (p *veggieMania) getPrice() int {
5     return 15
6 }

cheeseTopping.go

1 type cheeseTopping struct {
2     pizza pizza
3 }
4  
5 func (c *cheeseTopping) getPrice() int {
6     pizzaPrice := c.pizza.getPrice()
7     return pizzaPrice + 10
8 }

tomatoTopping.go

1 type tomatoTopping struct {
2     pizza pizza
3 }
4  
5 func (c *tomatoTopping) getPrice() int {
6     pizzaPrice := c.pizza.getPrice()
7     return pizzaPrice + 7
8 }

main.go

 1 func main() {
 2  
 3     veggiePizza := &veggieMania{}
 4  
 5     //Add cheese topping
 6     veggiePizzaWithCheese := &cheeseTopping{
 7         pizza: veggiePizza,
 8     }
 9  
10     //Add tomato topping
11     veggiePizzaWithCheeseAndTomato := &tomatoTopping{
12         pizza: veggiePizzaWithCheese,
13     }
14  
15     fmt.Printf("Price of veggieMania pizza with tomato and cheese topping is %d\n", veggiePizzaWithCheeseAndTomato.getPrice())
16  
17     peppyTofuPizza := &peppyTofu{}
18  
19     //Add cheese topping
20     peppyTofuPizzaWithCheese := &cheeseTopping{
21         pizza: peppyTofuPizza,
22     }
23  
24     fmt.Printf("Price of peppyTofu with tomato and cheese topping is %d\n", peppyTofuPizzaWithCheese.getPrice())
25  
26 }

输出内容:

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

 

posted @ 2023-04-21 14:14  格局打得开  阅读(42)  评论(0)    收藏  举报