装饰器模式
什么是装饰器模式?
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
上面说的可能有点抽象,简单来说,就是在原来的功能上进行扩展,给它新增一个或多个功能。有人可能会觉得,加功能就是往原来的逻辑上加代码。要注意的是这里说的是对功能进行扩展,而不是修改,就是保证原来的功能是不变的。
举例1
需求来了
我有一个接口,功能就是读取用户信息的,这是它的核心功能,核心函数就是读取数据库的函数。
现在因为接口请求很频繁,我要给它加一个缓存功能,以减轻数据库的请求压力。
- 读取用户信息
// 用户
type User struct{
}
func New() *User
// 查询用户信息
func (u *User) Read() {
}
func main() {
// 创建
u := New()
// 读取用户信息
u.Read()
}
- 现在为用户信息添加缓存
直接在上面加代码,上来就撸袖子加代码,不考虑设计模式
// 获取用户缓存
func GetUserCache(uid int) *User{
// ...
}
// 设置用户缓存
func SetUserCache(u *User) {
}
func main() {
uid := 123
// 用户信息有缓存
if u := GetUserCache(uid);u != nil {
return
}
// 创建
u := New()
// 读取用户信息
u.Read()
// 设置用户缓存
SetUserCache(u)
}
- 通过装饰器模式为用户添加缓存
// 用户缓存装饰器
type UserCacheDecorator struct {
}
// 创建一个装饰器
func NewUserCacheDecorator() *UserCacheDecorator {
return new(UserCacheDecorator)
}
//
func (UserCacheDecorator) Read(u *User) *User{
// 读取用户缓存
if u := GetUserCache(u.Id);u != nil {
如果缓存存在,则返回
return u
}
// 在装饰器里,调用真正的业务代码 - 读取用户信息
u.Read()
// 设置用户缓存
SetUserCache(u)
}
func main() {
user := New()
user.Id= 123
// 通过装饰器添加缓存
user = NewUserCacheDecorator().Read(user)
}
- 进阶版,多个装饰器去修饰读取用户信息的功能
// 用户
type User struct {
Id int
}
func New() *User {
return new(User)
}
// 查询用户信息
func (u *User) Read(uid int) *User {
u.Id = uid
fmt.Println("读取数据库信息", uid)
return u
}
// 用户装饰器抽象接口
type userDecoratorInter interface {
Read(uid int) *User
}
// 缓存用户信息的装饰器
type UserCacheDecorator struct {
u userDecoratorInter
}
func NewUserCacheDecorator(u userDecoratorInter) userDecoratorInter {
de := new(UserCacheDecorator)
de.u = u
return de
}
func (decorator *UserCacheDecorator) Read(uid int) *User {
// 读取用户缓存
if u := GetUserCache(uid); u != nil {
// 如果缓存存在,则返回
return u
}
// 读取用户信息
user := decorator.u.Read(uid)
// 设置用户缓存
SetUserCache(user)
return user
}
// 用户日志的装饰器
type UserLogDecorator struct {
u userDecoratorInter
}
func NewUserLogDecorator(u userDecoratorInter) userDecoratorInter {
de := new(UserLogDecorator)
de.u = u
return de
}
func (decorator *UserLogDecorator) Read(uid int) *User {
// 读取用户信息
user := decorator.u.Read(uid)
// 打印用户行为日志
fmt.Println("日志:用户读取信息...", uid)
return user
}
// 获取用户缓存
func GetUserCache(uid int) *User {
fmt.Println("获取用户缓存", uid)
return nil
}
// 设置用户缓存
func SetUserCache(u *User) {
fmt.Println("设置用户缓存", u)
}
func main() {
uid := 123
// 创建用户实例
user := New()
var decorator userDecoratorInter
// 用户缓存装饰器
decorator = NewUserCacheDecorator(user)
// 用户日志装饰器
decorator = NewUserLogDecorator(decorator)
// more decorator...
// 读取用户信息
decorator.Read(uid)
}
代码对比
引入装饰器的原因是,在不修改原来代码的情况下,对原有的功能进行扩展。在上面的例子中,我们要为用户添加缓存功能,但并不希望在原有的代码上修改,所以引入了缓存装饰器,同时还能扩展更多的装饰器,如日志装饰器等,这样能让代码更灵活。
代码对比举例2
gin的中间件
举例1可能说的不是很清楚,我们来看一下gin的中间件
gin的中间件其实也是使用装饰器的来实现的
{
// 这是gin路由
v1 := r.Group("/v1")
v1.POST("/point", Auth, ReceivePoint) // 假设,给用户加个积分
}
func Auth(c *gin.Context){
// 校验权限
}
func ReceivePoint(c *gin.Context){
// 处理逻辑
}
gin中间件执行流程
下面简化一下gin中间件的执行流程,方便阅读
// 1.添加中间件,在我们的路由层添加中间件函数,并与路由绑定
handlers := []gin.HandlerFunc{
Auth, ReceivePoint
}
// 2.生成c,这里的c是由gin生成,每个http request就会又一个c
c = &gin.Context{}
// 3.执行中间件
for _,handler := range handlers {
// 刚才的 Auth, ReceivePoint函数都在这里按顺序执行
handler(c)
}
上面的ReceivePoint()函数就是业务逻辑,它在这里是被装饰的功能,而Auth()函数是装饰功能。
我们可以这样理解,接口原有的功能就是给用户添加积分,即ReceivePoint()。但我希望在加积分之前,加一个验证token的功能,所以这里扩展了Auth()函数。而Auth和ReceivePoint之间代码是相互独立的,只是依赖了gin.Context。
gin源码
gin中间件的数据类型
只要实现了HandlerFunc类型的函数,都可以作为gin中间件,如上述的Auth, ReceivePoint函数
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
gin的Next函数
调用中间件的执行,基本是在Next()函数里面,同时它还可以控制中间件的执行顺序
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
装饰器使用场景
缓存:读写数据的缓存
gin的中间件:权限校验器、打印请求日志等
总结
优点
- 装饰器可以动态扩展一个实现类的功能,而不是在原有的功能代码上做修改,符合开放封闭原则。
- 装饰器和被装饰器之间可以独立发展,不会相互耦合。
缺点:
- 多层装饰设计起来会比较复杂;
网友评论