装饰器模式
装饰器模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,对于“增加对象功能”而言,装饰器模式比生成子类实现更为灵活,且装饰器模式可以无限叠加。
装饰器模式当中的角色和职责
装饰器模式的标准类图如下:
- Component(抽象构件):是具体构件和抽象装饰器的共同父类,声明了在具体构件中需要实现的业务方法。
- ConcreteComponent(具体构件):是抽象构件的子嘞,用于定义具体的构件对象。装饰器可以为它添加额外的职责(方法)。
装饰器模式的代码实现
参考《Easy 搞定 Golang 设计模式》,下面以“添加手机配件”为场景,实现一个使用装饰器模式的 Demo:
package main
import "fmt"
// --- --- --- 抽象层 --- --- ---
// 定义抽象的构件
type Phone interface {
Show() // 构件的功能
}
// 装饰器基础类, 本应该定义为 interface, 但由于 Golang 的 interface 只有方法没有成员, 所以定义为 struct
type Decorator struct {
phone Phone // 通过组合的方式实现 Decorator 基础类
}
func (d *Decorator) Show() {}
// --- --- --- 实现层 --- --- ---
// 定义具体的构件
type Apple struct{}
func (ap *Apple) Show() {
fmt.Println("正在使用苹果手机...")
}
type Realme struct{}
func (rm *Realme) Show() {
fmt.Println("正在使用真我手机...")
}
// 定义具体的装饰器
type TiemoDecorator struct {
Decorator // 继承基础的装饰器类(主要继承 Phone 的成员属性)
}
func (td *TiemoDecorator) Show() {
td.phone.Show() // 调用被装饰构件的原方法
fmt.Println("贴膜的手机")
}
func NewTiemoDecorator(phone Phone) Phone {
return &TiemoDecorator{Decorator{phone}}
}
type ShoujikeDecorator struct {
Decorator
}
func (sd *ShoujikeDecorator) Show() {
sd.phone.Show()
fmt.Println("加装手机壳的手机")
}
func NewShoujikeDecorator(phone Phone) Phone {
return &ShoujikeDecorator{Decorator{phone}}
}
func main() {
var iphone Phone
iphone = new(Apple)
iphone.Show() // 调用原构件的方法
var tiemoIPhone Phone
tiemoIPhone = NewTiemoDecorator(iphone)
tiemoIPhone.Show()
var shoujikeIPhone Phone
shoujikeIPhone = NewShoujikeDecorator(iphone)
shoujikeIPhone.Show()
var tiemoAndShoujikeIPhone Phone
tiemoAndShoujikeIPhone = NewShoujikeDecorator(tiemoIPhone)
tiemoAndShoujikeIPhone.Show()
}
装饰器模式与代理模式有何不同?
装饰器模式相较于代理模式而言,其动态性更好一些。可以根据不同的场景选择不同的模式,比如对于逻辑上需要增加的场景,可以使用代理模式;而对于需要无状态平行增加功能的场景,则可以选用装饰器模式,因为装饰器模式可以进行无状态的迭代。
装饰器模式的优缺点
优点
- 对于扩展一个对象的功能,装饰器模式比继承更加灵活,不会导致类的个数急剧增加;
- 可以通过一种动态的方式来扩展一个对象的功能,从而实现不同的行为;
- 可以对一个对象进行多次装饰(无状态地平行添加功能);
- 具体的构件类与具体的装饰器类可以独立变化,用户可以根据需要新增具体构件类和具体装饰器类,原有代码无需改变,符合“开闭原则”。
缺点
- 使用装饰器模式进行系统设计时会产生很多小的对象,大量小对象的产生势必会占用更多的系统资源,影响程序性能;
- 装饰器模式比继承更加灵活,但同时也意味着相较于继承,装饰器模式更容易出错。对于多次装饰的对象,如果出现问题,调试时需要逐级排查,较为繁琐。
适用场景
- 通过动态、透明的方式为单个对象添加职责;
- 当不能采用继承的方式对系统进行扩展或采用继承不利于系统扩展与维护时,可以使用装饰器模式。