装饰模式#
装饰模式是一种结构型模式,能够让我们通过将对象放入到包含行为的特殊封装对象,以此来向该对象绑定新的行为。
问题引入#
现在我们正在开发一个系统通知库,可以接收到客户端的消息参数,然后向目标客户发送对应的通知。目前支持Wechat
、QQ
、email
三种方式进行发送。
目前我们拥有一个基类BaseNotifier
其中定义了发送消息的这个行为以及一些成员变量。所以为了支持三个不同的消息发送方式,我们将通过继承,产生三种不同的子类。如下图所示:
但是有人会只想要收到
wechat
的消息,有人会想要收到QQ和wechat的消息。这样循环组合,或许更多的类诞生了:
代码量开始迅速膨胀,此时装饰模式就开始派上用场了。
问题解决#
通过扩展类的方式来改变一个对象的行为,这是我们大部分场景下第一个想到的方法,但存在着以下不可忽视的问题:
- 继承是静态的,我们不能在运行时更改该对象的已经确定好的行为,只能够使用不同的子类创建的对象来替代当前的整个对象。
- 大部分语言并不支持一个类继承多个父类。
Aggregation or Composition#
使用聚合或组合的思想,而不是继承。
聚合和组合的基本思想是,一个对象通过保存另一个对象的引用,该对象引用代表着某个行为的实施者。而不同于继承,后者则是通过包含所有父类的行为,自己实现目标行为。
使用这样的思想,我们可以轻松的在对象中包含所有需要的Helper
类,这样就可以在运行时改变容器的行为。**通过包含多个其他对象的引用,一个对象可以使用多个类的行为,将各种工作委派给对应的引用对象。**这是许多设计模式背后的关键原则。最生动形象的体现组合的例子是,穿衣服,穿得越多,叠加起来就暖和。
封装器,顾名思义就是包含着各个对象引用的和客户端进行交互的服务类的别称。因为封装器实现了和封装的所有对象的相同接口,在客户端看来,这些对象是完全一致的。封装器中的引用成员变量是遵循相同接口的任意对象。并且,封装其可以在委派工作之前,对请求进行前后处理,改变最后的请求结果。
客户端必须首先声明基础装饰器,之后逐步将需要的具体装饰器,放在自己所需要的装饰之中。这将形成一个由装饰对象形成的栈,最后一个进入栈中的装饰对象负责和客户端进行交互,它拥有栈内所有的对象的装饰行为。
代码示例#
这里通过层层嵌套decorator实现。首先定义基类QQNotifier
以及接口INotifier
,之后定义基本装饰器BaseDecorator
以及具体的装饰器的实现WechatDecorator
和EmailDecorator
type INotifier interface {
Send(msg string)
}
type BaseDecorator struct {
notifier INotifier
}
// 基类
type QQNotifier struct {
}
func (q *QQNotifier) Send(msg string) {
fmt.Println("[QQ]: ", msg)
}
// 基础装饰器
func (b *BaseDecorator) Send(msg string) {
if b.notifier != nil {
b.notifier.Send(msg)
}
}
type WeChatDecorator struct {
BaseDecorator
}
func (w *WeChatDecorator) Send(msg string) {
w.notifier.Send(msg)
fmt.Println("[wechat]: ", msg)
}
type EmailDecorator struct {
BaseDecorator
}
func (w *EmailDecorator) Send(msg string) {
w.notifier.Send(msg)
fmt.Println("[email]: ", msg)
}
func TestRun(t *testing.T) {
qq := &QQNotifier{}
// 开始加装饰器
// 第一层wechat
wechatDecorator := &WeChatDecorator{
BaseDecorator{notifier: qq},
}
// 第二层wechat
emailDecorator := &EmailDecorator{
BaseDecorator{notifier: wechatDecorator},
}
emailDecorator.Send("Hello World")
}
go运行结果:
[QQ]: Hello World
[wechat]: Hello World
[email]: Hello World
bash装饰模式结构#
应用场景#
- 如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式。
- 如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用该模式。
How To implement#
确保业务逻辑可用一个基本组件及多个额外可选层次表示。
找出基本组件和可选层次的通用方法。 创建一个组件接口并在其中声明这些方法。
创建一个具体组件类, 并定义其基础行为。
创建装饰基类, 使用一个成员变量存储指向被封装对象的引用。 该成员变量必须被声明为组件接口类型, 从而能在运行时连接具体组件和装饰。 装饰基类必须将所有工作委派给被封装的对象。
确保所有类实现组件接口。
将装饰基类扩展为具体装饰。 具体装饰必须在调用父类方法 (总是委派给被封装对象) 之前或之后执行自身的行为。
客户端代码负责创建装饰并将其组合成客户端所需的形式。