大家好,我是晴天。我们又见面了,本周我们再来学一种设计模式------装饰器模式。准备好了嘛,let's go
什么是装饰器模式
基础概念
装饰器设计模式是一种结构型设计模式,它允许你通过将原始对象(基础组件)包装在一个特殊类(装饰器)的实例中来为原始对象添加新的行为和责任,而无需修改其代码。这种模式允许你通过在运行时动态地添加新功能来扩展原始对象的行为。
基本组成
在装饰器模式中,通常有一个基础组件(ConcreteComponent)定义了基本行为,然后有一个或多个装饰器(Decorator)类,它们包装了基础组件并添加了额外的功能。装饰器类和基础组件类都实现了相同的接口,以便它们可以互换使用。
举例说明
这样说起来有点抽象,我们还是举一个例子来理解一下这句话。 我们现在要吃一顿饭,现在有白米饭和面条两种主食供我们挑选,在挑选完主食之后,我们可以向这两种主食上加各种各样的配菜。假设我们选择了白米饭,配菜选择了鱼香肉丝,那么我们就把这道白米饭装饰成了鱼香肉丝盖浇饭,再加两个鸡蛋,这道菜就变成了加蛋版鱼香肉丝盖浇饭(还可以无限添加喜欢的配菜)。白米饭或者面条就是原始对象,鱼香肉丝和鸡蛋就是特殊类,通过特殊类给原始对象添加新功能,这个原始对象就变成了经过装饰器装饰的新对象了。
为什么需要装饰器模式
可能会有细心的同学发现,装饰器类不就是给类添加新的功能嘛,那我通过类继承的方式,也可以给子类添加新的功能,实现相同的功能呀。
是的,使用继承也能实现相同的功能,但是继承会有一些问题。见下图。
继承方式存在的问题
- 使用继承来添加功能可能导致类的爆炸性增长,因为每个组合的功能都需要创建一个新的子类。
- 继承可能导致一个类具有多个责任,从而违反单一职责原则。
- 继承在现有的功能类下,进行新的组合,必须要增加代码,重新继承父类,实现新功能。
装饰器的优势
- 装饰器模式可以在不修改现有代码的情况下引入新的功能。通过将新功能包装在装饰器中,可以在运行时动态地组合和应用这些功能,而无需修改现有类的代码,符合开闭原则。
- 没有子类爆炸性增长的问题,都是各种装饰类对象的任意组合,更加灵活,组合的方式更多样性。
- 每个具体的装饰器只负责添加一个特定的功能,符合单一职责原则。
装饰器模式的用法
说了这么多,我们来看一下具体的装饰器类的代码实现吧。
go
package main
import (
"fmt"
"testing"
)
// 基础组件抽象层
// 抽象 基础组件 Component
type Food interface {
Eat() string
Pay() int64
}
// 基础组件实现层
// 具体组件 ConcreteComponent
type Rice struct {
}
func (r *Rice) Eat() string {
return "吃米饭"
}
// 支付金额
func (r *Rice) Pay() int64 {
return 1
}
// 具体组件 ConcreteComponent
type Noodle struct {
}
func (n *Noodle) Eat() string {
return "吃面条"
}
func (n *Noodle) Pay() int64 {
return 2
}
// 装饰器抽象层
// 装饰器 Decorator
type Decorator Food
// 创建一个装饰器
func NewDecorator(d Decorator) Decorator {
return d
}
// 装饰器实现层
// 具体装饰器类 鸡蛋装饰器 ConcreteDecorator
type EggDecorator struct {
Decorator
}
func NewEggDecorator(d Decorator) Decorator {
return &EggDecorator{d}
}
func (e *EggDecorator) Eat() string {
return e.Decorator.Eat() + " 加鸡蛋"
}
func (e *EggDecorator) Pay() int64 {
return e.Decorator.Pay() + 3
}
// 具体装饰器类 酱汁装饰器 ConcreteDecorator
type SauceDecorator struct {
Decorator
}
func NewSauceDecorator(d Decorator) Decorator {
return &SauceDecorator{d}
}
func (s *SauceDecorator) Eat() string {
return s.Decorator.Eat() + " 加酱汁"
}
func (s *SauceDecorator) Pay() int64 {
return s.Decorator.Pay() + 4
}
// 业务逻辑层,组装一顿饭
func TestDecorator(t *testing.T) {
// 基础组件 白米饭
var rice Food
rice = &Rice{}
fmt.Println(rice.Eat())
fmt.Println(rice.Pay())
// 基础组件 面条
var noodle Food
noodle = &Noodle{}
fmt.Println(noodle.Eat())
fmt.Println(noodle.Pay())
// 加菜了 创建一个装饰器
var eggDecorator Decorator
// 在白米饭上添加鸡蛋
eggDecorator = NewEggDecorator(rice)
fmt.Println(eggDecorator.Eat())
fmt.Println(eggDecorator.Pay())
var sauceDecorator Decorator
// 继续加菜 在白米饭加鸡蛋的基础上再添加酱汁
sauceDecorator = NewSauceDecorator(eggDecorator)
fmt.Println(sauceDecorator.Eat())
fmt.Println(sauceDecorator.Pay())
}
// 输出结果
吃米饭
1
吃面条
2
吃米饭 加鸡蛋
4
吃米饭 加鸡蛋 加酱汁
8
上述代码定义了基础组件类 Food,两个具体组件类Rice白米饭和Noodle面条。一个抽象的装饰器类,两个具体的装饰器类,EggDecorator鸡蛋装饰器和SauceDecorator酱汁装饰器。 在业务逻辑层,组装了加鸡蛋的白米饭和加鸡蛋 and 酱汁的白米饭。
装饰器模式和代理模式的区别
细心的小伙伴可能发现装饰器模式和代理模式很相似,没学过代理模式的小伙伴可以看一下这篇文章。一文搞懂设计模式之代理模式
其实装饰器模式和代理模式还是有些区别的。
装饰器模式
- 目的: 装饰器模式的主要目的是动态地为对象添加新的功能,而无需改变其结构。它通过将对象包装在一个装饰器类的实例中来实现,这个装饰器类实现了相同的接口,并在调用基础对象的同时添加额外的功能。
- 结构: 装饰器模式包含一个基础组件接口、具体的基础组件类、装饰器接口、具体的装饰器类等组件。
- 使用场景: 当需要在运行时动态地添加、修改或删除对象的功能时,装饰器模式是一个有用的选择。它允许你以透明的方式扩展对象的功能。
代理模式
- 目的: 代理模式的主要目的是控制对对象的访问,通常是为了提供一些额外的控制,而不是为了添加新的功能。代理模式可以用于实现延迟加载、访问控制、监控等。
- 结构: 代理模式包含一个主题接口、具体主题类、代理接口、具体代理类等组件。代理类通常在其内部持有一个对真实主题的引用。
- 使用场景: 当需要在访问某个对象时进行一些额外的控制,例如权限验证、记录日志、延迟加载等情况下,代理模式是一个有用的选择。
区别
- 目的不同: 装饰器模式的主要目的是动态地为对象添加新的功能,而代理模式的主要目的是控制对对象的访问。
- 实现方式不同: 装饰器模式通过包装对象实现,而代理模式通过引入一个代理类来控制访问。
- 使用场景不同: 装饰器模式适用于需要在运行时动态添加功能的情况,而代理模式适用于控制对对象的访问的情况。
总结
本文,我们介绍了什么是装饰器模式,为什么需要装饰器模式,装饰器模式的使用方法,并且对比了装饰器模式和代理模式,总结了一些相同点和不同点。希望各位读者小伙伴能有些许收获。
写在最后
感谢大家的阅读,晴天将继续努力,分享更多有趣且实用的主题,如有错误和纰漏,欢迎留言给予指正。 更多文章敬请关注作者个人公众号 晴天码字。 我们下期不见不散,to be continued...
本文由mdnice多平台发布