1.装饰器模式

装饰器模式

什么是装饰器模式?

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

上面说的可能有点抽象,简单来说,就是在原来的功能上进行扩展,给它新增一个或多个功能。有人可能会觉得,加功能就是往原来的逻辑上加代码。要注意的是这里说的是对功能进行扩展,而不是修改,就是保证原来的功能是不变的。

举例1

需求来了

我有一个接口,功能就是读取用户信息的,这是它的核心功能,核心函数就是读取数据库的函数。
现在因为接口请求很频繁,我要给它加一个缓存功能,以减轻数据库的请求压力。

  1. 读取用户信息
// 用户
type User struct{
}

func New() *User 

// 查询用户信息
func (u *User) Read() {
    
}

func main() {
    // 创建
    u := New()
    
    // 读取用户信息
    u.Read()
}

  1. 现在为用户信息添加缓存
    直接在上面加代码,上来就撸袖子加代码,不考虑设计模式
// 获取用户缓存
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)
    
}

  1. 通过装饰器模式为用户添加缓存

// 用户缓存装饰器
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)
    
}
  1. 进阶版,多个装饰器去修饰读取用户信息的功能
// 用户
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的中间件:权限校验器、打印请求日志等

总结

优点

  1. 装饰器可以动态扩展一个实现类的功能,而不是在原有的功能代码上做修改,符合开放封闭原则
  2. 装饰器和被装饰器之间可以独立发展,不会相互耦合

缺点:

  1. 多层装饰设计起来会比较复杂;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容