装饰器模式是一种软件设计模式,可让您在现有逻辑之上添加更多功能。为了解决这个问题,人们首先想到的是使用继承——这是完全有道理的。然而,继承的本质是静态的。如果您有附加功能的多种变体或更糟糕的是它们的各种组合,则您必须将所有可能的组合创建到扩展基类的单独类中。在这些情况下,您的代码库的大小会迅速增加,会降低其可维护性。
什么是装饰者模式
装饰器模式的实现需要您尝试扩展以实现基本接口的基本逻辑 :
- 一个定义:它具有哪些方法以及它们产生什么的契约。
- 然后,您可以创建实现基接口的类,同时包含另一个也实现该接口的内部对象。
- 您要重写的接口方法内部的逻辑可以重用内部对象的实现,同时向其添加其他逻辑 - 甚至完全忽略基本逻辑,这取决于您。
此外,甚至完全有可能放置任何已经具有扩展逻辑的类,反过来装饰器将在它们之上添加更多逻辑。package main
import "fmt"
type VanillaIceCream struct{}
func (v *VanillaIceCream) GetIceCream() string { return "Vanilla IceCream" }
|
假设有两个人:本和迈克想要冰淇淋。每个人都想要定制自己的冰淇淋配置,本更喜欢上面有巧克力糖霜的香草冰淇淋,而迈克更喜欢上面有巧克力糖霜和焦糖酱的香草冰淇淋(甚至不确定这是否是一回事)。解决这个问题的继承方法是创建一个满足所需组合的全新类。
因此,附加类:
type VanillaIceCreamWithChocolateFrostings struct{ VanillaIc VanillaIceCream }
func (v *VanillaIceCreamWithChocolateFrostings) GetIceCream() string { return v.VanillaIc.GetIceCream() + " with Chocolate Frostings" }
type VanillaIceCreamWithChocolateFrostingsWithCaramelSauce struct{ VanillaIcWithChoco VanillaIceCreamWithChocolateFrostings }
func (v *VanillaIceCreamWithChocolateFrostingsWithCaramelSauce) GetIceCream() string { return v.VanillaIcWithChoco.GetIceCream() + " with Chocolate Frostings and Caramel Sauce" }
|
然后,另一个人约翰加入了他们,想要一份仅加焦糖酱的香草冰淇淋。
type VanillaIceCreamWithCaramelSauce struct{ VanillaIc VanillaIceCream }
func (v *VanillaIceCreamWithCaramelSauce) GetIceCream() string { return v.VanillaIcWithChoco.GetIceCream() + " with caramel sauce" }
|
现在,考虑更多你能想到的配料,并遵循我们迄今为止所做的。您已经可以预测如果这种情况持续下去,代码将会有多大。使用装饰器模式,您只需声明一个接口,该接口返回冰淇淋类实现的实际口味,并让您的装饰器类遵循它们。
package main
import "fmt"
type IIceCream interface { GetIceCream() string }
type VanillaIceCream struct{}
func (v *VanillaIceCream) GetIceCream() string { return "Vanilla IceCream" }
type ChocolateFrostingDecorator struct { IceCream IIceCream }
func (c *ChocolateFrostingDecorator) GetIceCream() string { return c.IceCream.GetIceCream() + " with Chocolate Frosting" }
type CaramelSauceDecorator struct { IceCream IIceCream }
func (c *CaramelSauceDecorator) GetIceCream() string { return c.IceCream.GetIceCream() + " with Caramel Sauce" }
func main() { iceCreamOne := &VanillaIceCream{} iceCreamOneWithChocolateFrosting := &ChocolateFrostingDecorator{IceCream: iceCreamOne} iceCreamOneWithChocolateFrostingWithCaramelSauce := &CaramelSauceDecorator{IceCream: iceCreamOneWithChocolateFrosting} fmt.Println("iceCream1: " + iceCreamOneWithChocolateFrostingWithCaramelSauce.GetIceCream())
iceCreamTwo := &VanillaIceCream{} iceCreamTwoWithChocoFrosting := &ChocolateFrostingDecorator{IceCream: iceCreamTwo} iceCreamTwoWithChocoFrWithCaramelSauce := &CaramelSauceDecorator{IceCream: iceCreamTwoWithChocoFrosting} fmt.Println("iceCream2: " + iceCreamTwoWithChocoFrWithCaramelSauce.GetIceCream())
iceCreamThree := &VanillaIceCream{} iceCreamThreeWithCaramelFrostingOnly := &CaramelSauceDecorator{IceCream: iceCreamThree} fmt.Println("iceCream3: " + iceCreamThreeWithCaramelFrostingOnly.GetIceCream()) }
|
如果其他人想要相同的自定义,但需要香蕉冰淇淋,只要 Banana IceCream 类实现基本接口,现有的装饰器就可以很好地处理它们。
// ...
type BananaIceCream struct{}
func (b *BananaIceCream) GetIceCream() string { return "Banana IceCream" }
// ...
func main() { // ...
iceCreamFour := &BananaIceCream{} iceCreamFourWithCaramelSauceOnly := &CaramelSauceDecorator{IceCream: iceCreamFour} fmt.Println("iceCream4: " + iceCreamFourWithCaramelSauceOnly.GetIceCream()) }
|
让我们增加问题的复杂性,并考虑现有口味需要以不同顺序堆叠的情况。通过继承,您可以将现有代码量增加一倍甚至三倍。装饰器模式解锁了可能的解决方案,例如接受一堆风格并循环遍历每个风格(可能使用递归)并在每次使用 switch case 语句时应用适当的装饰器。装饰器模式的缺点
您从一开始就被迫定义要应用的装饰器的顺序 - 创建新的装饰器比中途更改它们的顺序或更糟要容易得多。
它肯定还有更多的弱点,但本文中提到的一个是我发现最明显的一个。
结论
探索设计模式可以避免编写大型项目时常见的陷阱。
装饰器模式可帮助您以更易于维护的方式在现有代码之上堆叠额外的逻辑。