Decorator(装饰器)设计模式在redigo中的应用
今天来看一看Decorator(装饰器)设计模式在redigo(github: redigo)中的应用。
先来看一下redigo的interface Conn
以及相关结构体的UML图。
从图中可以看出以下几点:
conn
、pooledConnection
、loggingConn
和errorConnection
均实现了Conn
接口(虚线的箭头)pooledConnection
、loggingConn
不但实现了Conn
接口,还拥有该接口类型的成员(尾部是实心棱形的箭头)conn
与interface 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", )
// logger: 2023/09/22 18:37:38 redigo: .Do(GET, "foo") -> ("bar", )
// logger: 2023/09/22 18:37:38 redigo: .Do(GET, "none") -> (, )
即使改变LoggingConnection和PooledConnection的嵌套顺序,也不影响输出结果。这就好像“蔚蓝的天空已被黑色的,乌鸦的,丑陋的翅膀掩蔽“与”乌鸦的,黑色的,丑陋的翅膀“并没有太大差别一样。
(来源:图解|《中国最后一个太监》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", )
// logger: 2023/09/22 19:29:26 redigo: .Do(GET, "foo") -> ("bar", )
// logger: 2023/09/22 19:29:26 redigo: .Do(GET, "none") -> (, )
甚至还可以再套一层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", )
// another logger: 2023/09/22 19:40:50 REDIGO: .Do(SET, "foo", "bar") -> ("OK", )
// logger: 2023/09/22 19:40:50 redigo: .Do(GET, "foo") -> ("bar", )
// another logger: 2023/09/22 19:40:50 REDIGO: .Do(GET, "foo") -> ("bar", )
另外,相较于GOF中的结构,
- redigo中没有相当于图中
Decorator
的抽象类 struct conn
相当于图中的ConcreteComponent
,是被修饰的对象struct pooledConnection
、struct loggingConn
相当于ConcreteDecoratorA
或ConcreteDecoratorB
,既能修饰conn
又能相互修饰
值得一提的是,struct errorConnection
采用了称为Null Object或Special 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}
}
- Special Case martinfowler.com/eaaCatalog/…
- Null Object en.wikipedia.org/wiki/Null_o…