likes
comments
collection
share

Decorator(装饰器)设计模式在redigo中的应用

作者站长头像
站长
· 阅读数 28

Decorator(装饰器)设计模式在redigo中的应用

今天来看一看Decorator(装饰器)设计模式在redigo(github: redigo)中的应用。

先来看一下redigo的interface Conn以及相关结构体的UML图。

Decorator(装饰器)设计模式在redigo中的应用

从图中可以看出以下几点:

  • connpooledConnectionloggingConnerrorConnection均实现了Conn接口(虚线的箭头)
  • pooledConnectionloggingConn不但实现了Conn接口,还拥有该接口类型的成员(尾部是实心棱形的箭头)
  • conninterface Conn之间没有尾部是实心棱形的箭头,因为conn是要被其他组件修饰的、最基本的组件

这种结构就是典型的Decorator(装饰器)设计模式的结构,我们很容易基于此写出**Conn的实现嵌套了Conn的实现**的代码,例如,

package main

import (
    "github.com/garyburd/redigo/redis"
    "log"
    "os"
      "sync"
    "time"
)

var logger *log.Logger

func init() {
    logger = log.New(os.Stderr, "logger: ", log.LstdFlags)
}

func main() {
    pooledLoggableRedisConnection := redis.NewLoggingConn( // ③
        NewPooledConn( // ②
            NewLowLevelConn("tcp", ":6379")), // ①
        logger,
        "redigo: ")

    pooledLoggableRedisConnection.Do("SET", "foo", "bar")
    pooledLoggableRedisConnection.Do("GET", "foo")
    pooledLoggableRedisConnection.Do("GET", "none")

}

NewLowLevelConn()用于创建最底层、最基本的连接。

func NewLowLevelConn(network, address string) redis.Conn {
    conn, err := redis.Dial(network, address)
    if err != nil {
        logger.Println(err)
        return nil
    }
    return conn
}

而②NewPooledConn()用于将最底层、最基本的连接纳入连接池的管理(Pooled)。

var pool *redis.Pool
var once sync.Once
func NewPooledConn(lowLevelConn redis.Conn) redis.Conn {
    once.Do(func() {
        pool = &redis.Pool{
            MaxIdle:     3,
            IdleTimeout: 240 * time.Second,
            Dial: func() (redis.Conn, error) {
                return lowLevelConn, nil
            },
        }
    })
    return pool.Get()
}

最外层的③redis.NewLoggingConn()则提供了记录Redis请求和响应的功能。也就是说,每套上了一层就在最基本的连接上增加了一层功能

pooledLoggableRedisConnection.Do("SET", "foo", "bar")
pooledLoggableRedisConnection.Do("GET", "foo")
pooledLoggableRedisConnection.Do("GET", "none")
    
// logger: 2023/09/22 18:37:38 redigo: .Do(SET, "foo", "bar") -> ("OK", <nil>)
// logger: 2023/09/22 18:37:38 redigo: .Do(GET, "foo") -> ("bar", <nil>)
// logger: 2023/09/22 18:37:38 redigo: .Do(GET, "none") -> (<nil>, <nil>)

即使改变LoggingConnection和PooledConnection的嵌套顺序,也不影响输出结果。这就好像“蔚蓝的天空已被黑色的,乌鸦的,丑陋的翅膀掩蔽“与”乌鸦的,黑色的,丑陋的翅膀“并没有太大差别一样。

Decorator(装饰器)设计模式在redigo中的应用

(来源:图解|《中国最后一个太监》kknews.cc/entertainme…

pooledLoggableRedisConnection := NewPooledConn(
    redis.NewLoggingConn(
        NewLowLevelConn("tcp", ":6379"),
        logger,
        "redigo: "))
pooledLoggableRedisConnection.Do("SET", "foo", "bar")
pooledLoggableRedisConnection.Do("GET", "foo")
pooledLoggableRedisConnection.Do("GET", "none")
    
// logger: 2023/09/22 19:29:26 redigo: .Do(SET, "foo", "bar") -> ("OK", <nil>)
// logger: 2023/09/22 19:29:26 redigo: .Do(GET, "foo") -> ("bar", <nil>)
// logger: 2023/09/22 19:29:26 redigo: .Do(GET, "none") -> (<nil>, <nil>)

甚至还可以再套一层redis.NewLoggingConn(),将日志写入到另外的文件中。

anotherLogger := log.New(os.Stderr, "another logger: ", log.LstdFlags)
pooledDoubleLoggerRedisConnection := redis.NewLoggingConn(
    NewPooledConn(
        redis.NewLoggingConn(
            NewLowLevelConn("tcp", ":6379"),
            logger,
            "redigo: ")),
    anotherLogger,
    "REDIGO: ")
pooledDoubleLoggerRedisConnection.Do("SET", "foo", "bar")
pooledDoubleLoggerRedisConnection.Do("GET", "foo")

// logger: 2023/09/22 19:40:50 redigo: .Do(SET, "foo", "bar") -> ("OK", <nil>)
// another logger: 2023/09/22 19:40:50 REDIGO: .Do(SET, "foo", "bar") -> ("OK", <nil>)
// logger: 2023/09/22 19:40:50 redigo: .Do(GET, "foo") -> ("bar", <nil>)
// another logger: 2023/09/22 19:40:50 REDIGO: .Do(GET, "foo") -> ("bar", <nil>)

另外,相较于GOF中的结构,

Decorator(装饰器)设计模式在redigo中的应用

  • redigo中没有相当于图中Decorator的抽象类
  • struct conn相当于图中的ConcreteComponent,是被修饰的对象
  • struct pooledConnectionstruct loggingConn相当于ConcreteDecoratorAConcreteDecoratorB,既能修饰conn又能相互修饰

值得一提的是,struct errorConnection采用了称为Null ObjectSpecial Case的设计模式,这样即使发生了错误,也能从连接池中获取实现了interface Conn的连接,而不是nil。

// ...
// This method always returns a valid connection so that applications can defer error handling to the first use of the connection.
// ...
func (p *Pool) Get() Conn {
    c, err := p.get()
    if err != nil {
        return errorConnection{err}
    }
    return &pooledConnection{p: p, c: c}
}