介绍
装饰器模式(Decorator Pattern)是一种结构型设计模式,通过组合代替继承,它允许动态地向对象添加额外的功能,同时又不改变其接口。装饰器模式通过将对象包裹在一个装饰器类中,从而在运行时扩展对象的行为。
为什么组合优于继承?
- 灵活性(Flexibility):组合允许对象在运行时动态地改变其行为。对象可以通过组合其他对象来获取所需的功能,并且可以在运行时更改组合的对象,从而实现灵活的行为扩展。相比之下,继承在编译时固定了类的行为,不够灵活。
- 松耦合(Loose Coupling):组合通过对象之间的接口进行交互,而不是依赖于具体的实现细节。这导致对象之间的耦合度更低,更容易理解、维护和扩展。继承会导致类与其子类之间紧密关联,子类的修改可能会影响到父类和其他子类,增加了耦合度。
- 代码复用(Code Reusability):组合可以通过将多个对象组合在一起来实现代码的复用。对象之间的功能可以通过组合不同的对象来组合出新的功能,从而避免了重复编写相似的代码。继承也可以实现代码复用,但容易导致继承层次的复杂性,而组合相对更简单明了。
- 可扩展性(Extensibility):组合允许在不修改现有代码的情况下添加新的功能。通过添加新的组合对象,可以在不影响现有功能的基础上扩展对象的行为。继承在添加新功能时可能需要修改现有的类层次结构,从而影响到其他相关的类。
- 解耦继承的限制(Avoiding Inheritance Restrictions):继承有一些限制,例如单继承的限制、继承层次的复杂性等。组合没有这些限制,可以更自由地组织对象之间的关系。
涉及的角色有:
- Component(组件):定义了一个抽象接口,可以是抽象类或接口,描述了被装饰对象和装饰器对象的共同行为。
- ConcreteComponent(具体组件):实现了组件接口,是被装饰的对象。它是装饰器模式中的初始对象,可以在运行时动态地添加额外的功能。
- Decorator(装饰器):实现了组件接口,并且内部持有一个组件对象的引用。装饰器具有与组件相同的接口,并且可以包裹(装饰)其他装饰器或具体组件对象。装饰器可以在调用组件的方法之前或之后执行额外的行为,并且可以通过递归组合,以实现多层装饰。
- ConcreteDecorator(具体装饰器):具体装饰器是装饰器模式中的具体类,通过扩展装饰器类来添加特定的功能或行为。
原理
- 创建一个具体组件对象,即被装饰的对象。
- 创建一个或多个具体装饰器对象,它们实现了组件接口,并且内部持有一个组件对象的引用。
- 通过将具体装饰器对象包裹在具体组件对象上,形成装饰器链。
- 当调用具体组件对象的方法时,装饰器链会依次执行额外的行为,并最终调用具体组件对象的方法。
优点
- 可以在运行时动态地向对象添加额外的功能,而无需修改现有代码。通过使用不同的装饰器组合,可以实现各种功能的灵活组合。
- 装饰器模式将功能划分为多个具体装饰器类,每个装饰器类只关注特定的功能,使得类的职责更加清晰。
缺点
- 装饰器模式引入了许多具体装饰器类,可能会增加类的数量,使得代码更加复杂。
- 如果使用过多的具体装饰器类,可能会导致系统中存在过多的细粒度对象,使得代码难以维护。
使用场景
- 在不修改现有代码的情况下,动态地给对象添加功能。
- 需要对对象的功能进行灵活组合和排列组合。
- 需要为对象添加额外的行为,但是继承关系不适用或不可行。
- 需要在运行时动态地添加、修改或移除对象的功能。
- 需要对多个对象分别添加功能,而不影响其他对象。
比如:
- 日志记录/性能统计:在方法调用前后记录日志信息,例如记录方法的输入参数、返回值以及执行时间等。这样可以在不修改原始方法的情况下,方便地添加日志记录功能。
- 缓存:通过在方法调用前先检查缓存中是否存在结果,如果存在则直接返回缓存的结果,如果不存在则调用原始方法,并将结果存入缓存中。
- 输入验证:通过在方法调用前先验证输入的数据是否符合要求,如果不符合则抛出异常或进行其他处理。
- 权限控制:通过在方法调用前先检查用户的权限,如果用户有权限则继续执行方法,如果没有权限则抛出异常或进行其他处理。
- 数据加密:通过在方法调用前先对数据进行加密,然后在方法调用后对数据进行解密。
- 功能切换:通过在方法调用前后切换不同的装饰器,可以实现不同的功能组合。这样可以根据需求灵活地切换对象的功能。
代码示例
日志记录:
package main
import (
"fmt"
"log"
"time"
)
// 定义Logger接口,包含Log方法,用于日志记录
type Logger interface {
Log(string)
}
// 定义DefaultLogger结构体
type DefaultLogger struct{}
// 实现Logger接口中的Log方法,默认的日志记录方式
func (d DefaultLogger) Log(msg string) {
log.Println(msg)
}
// 定义DecoratorFunc装饰器函数类型,接受一个string参数,表示需要记录的日志信息
type DecoratorFunc func(string)
// 定义LogDecorator函数,接受一个Logger参数,并返回一个装饰了日志记录功能的新的函数
func LogDecorator(logger Logger) DecoratorFunc {
// 返回一个匿名函数
return func(msg string) {
start := time.Now()
// 添加日志开始时间戳
logger.Log(fmt.Sprintf("Start: %s", start))
// 匿名函数结束后执行
defer func() {
// 添加日志结束时间戳
logger.Log(fmt.Sprintf("End: %s%v", msg, time.Since(start)))
}()
}
}
func main() {
// 创建DefaultLogger实例,传递给LogDecorator函数
logger := DefaultLogger{}
// 定义LogDecorator函数返回的装饰器函数
decoratedLogger := LogDecorator(logger)
// 调用decoratedLogger函数,传递需要记录的日志信息
decoratedLogger("use time: ")
}
组合:
package main
import "fmt"
type Jntm interface {
Sing()
Dance()
}
type Ikun struct{}
func (i *Ikun) Sing() {
fmt.Println("唱")
}
func (i *Ikun) Dance() {
fmt.Println("跳")
}
type Rap struct {
Jntm
}
func (r *Rap) Sing() {
//r.Jntm.Sing()
fmt.Println("rap")
}
type Basketball struct {
Jntm
}
func (b *Basketball) Sing() {
//b.Jntm.Sing()
fmt.Println("篮球")
}
func main() {
fmt.Println("---ikun---")
ikun := &Ikun{}
ikun.Sing()
ikun.Dance()
rap := &Rap{
Jntm: ikun,
}
rap.Sing()
basketball := &Basketball{
Jntm: ikun,
}
basketball.Sing()
}
代码实现中没有Decorator类,主要是因为Go组合的特性。之所以有Decorator,是因为Decorator中有component成员变量,Decorator中函数实现是调用component的函数,所以对于component中的每一个函数,Decorator都需要封装一下,否则无法使用。但是Go组合方式会自动完成这项任务,无需封装,自然也就不需要Decorator了。
装饰器嵌套
package main
import "fmt"
// 定义Photo接口,包含一个GetPhoto方法,用于获取照片信息
type Photo interface {
GetPhoto() string
}
// 定义BasicPhoto结构体
type BasicPhoto struct{}
// 实现Photo接口的GetPhoto方法,表示基础照片
func (bp *BasicPhoto) GetPhoto() string {
return "基础图像"
}
// 定义FilterDecorator结构体,包含photo字段
type FilterDecorator struct {
photo Photo
}
// 实现了Photo接口的GetPhoto方法,表示被装饰的照片
func (fd *FilterDecorator) GetPhoto() string {
return fd.photo.GetPhoto()
}
// 定义BlackAndWhiteFilter结构体,继承自FilterDecorator结构体,表示黑白滤镜装饰器
type BlackAndWhiteFilter struct {
FilterDecorator
}
// 实现了Photo接口的GetPhoto方法,给照片添加黑白滤镜
func (bwf *BlackAndWhiteFilter) GetPhoto() string {
return fmt.Sprintf("给%s加上黑白滤镜", bwf.FilterDecorator.GetPhoto())
}
func main() {
// 创建一个BasicPhoto实例
photo := &BasicPhoto{}
// 并将其传递给BlackAndWhiteFilter结构体的FilterDecorator字段
filterPhoto := &BlackAndWhiteFilter{
FilterDecorator: FilterDecorator{
photo: photo,
},
}
// 调用GetPhoto方法
result := filterPhoto.GetPhoto()
fmt.Println(result) // 给基础图像加上黑白滤镜
}