美文网首页
1.装饰器模式

1.装饰器模式

作者: GUIN蚂蚁 | 来源:发表于2021-12-23 17:07 被阅读0次

    装饰器模式

    什么是装饰器模式?

    装饰器模式(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. 多层装饰设计起来会比较复杂;

    相关文章

      网友评论

          本文标题:1.装饰器模式

          本文链接:https://www.haomeiwen.com/subject/zfahqrtx.html