适配器模式:使用“适配器”,将原本原本由于接口不兼容而不能一起工作的那些类可以在一起工作
适配器模式中的角色构成如下:
客户端代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。如果有需求可以向程序中添加新类型的适配器而无需修改已有适配器实现。
//Target 适配器接口,描述客户端和被适配服务间约定的接口
type Target interface {
Request() string
}
//Adaptee 是被适配的目标接口
type Adaptee interface {
SpecificRequest() string
}
//NewAdaptee 是被适配接口的工厂函数
func NewAdaptee() Adaptee {
return &adapteeImpl{}
}
//AdapteeImpl 是被适配的目标类
type adapteeImpl struct{}
//SpecificRequest 是目标类的一个方法
func (*adapteeImpl) SpecificRequest() string {
return "adaptee method"
}
//NewAdapter 是Adapter的工厂函数
func NewAdapter(adaptee Adaptee) Target {
return &adapter{
Adaptee: adaptee,
}
}
//Adapter 是转换Adaptee为Target接口的适配器
type adapter struct {
Adaptee
}
//Request 实现Target接口
func (a *adapter) Request() string {
return a.SpecificRequest()
}
客户端代码直接通过适配器来间接使用被适配对象的功能,解决了两者不兼容的问题。
import "testing"
var expect = "adaptee method"
func TestAdapter(t *testing.T) {
adaptee := NewAdaptee()
target := NewAdapter(adaptee)
res := target.Request()
if res != expect {
t.Fatalf("expect: %s, actual: %s", expect, res)
}
}
建议引入依赖库的时候使用适配器模式?项目使用第三方类库的时候,防止未来有更换同等功能类库的可能,一般会推荐使用适配器模式对三方类库做一层封装,这样未来需要用同等功能的服务类进行替换时,实现一个新的适配器包装服务类即可,不需要对已有的客户端代码进行更改。
使用适配器模式,在项目中接入依赖库,这样以后需要替换成其他同等功能的依赖库的时候,不会影响到项目中的通过适配器使用依赖库功能的代码。
import (
...
"github.com/gomodule/redigo/redis"
)
// Cache 定义适配器实现类要实现的接口
type Cache interface {
Put(key string, value interface{})
Get(key string) interface{}
GetAll(keys []string) map[string]interface{}
}
定义适配器实现类, RedisCache 类型会Cache接口,同时我们为RedisCache提供一个工厂方法,在工厂方法里进行 Redis 链接池的初始化
// RedisCache 实现适配器接口
type RedisCache struct {
conn *redis.Pool
}
// RedisCache的工厂方法
func NewRedisCache() Cache {
cache := &RedisCache{
conn: &redis.Pool{
MaxIdle: 7,
MaxActive: 30,
IdleTimeout: 60 * time.Second,
Dial: func() (redis.Conn, error) {
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println(err)
return nil, err
}
if _, err := conn.Do("SELECT", 0); err != nil {
conn.Close()
fmt.Println(err)
return nil, err
}
return conn, nil
},
},
}
return cache
}
RedisCache实现 Cache 适配器接口的方法,这三个方法实现分别对应Redis的 SET、GET和MGET操作
// 缓存数据
func (rc *RedisCache) Put(key string, value interface{}) {
if _, err := rc.conn.Get().Do("SET", key, value); err != nil {
fmt.Println(err)
}
}
// 获取缓存中指定的Key的值
func (rc *RedisCache) Get(key string) interface{} {
value, err := redis.String(rc.conn.Get().Do("GET", key))
if err != nil {
fmt.Println(err)
return ""
}
return value
}
// 从缓存获取多个Key的值
func (rc *RedisCache) GetAll(keys []string) map[string]interface{} {
intKeys := make([]interface{}, len(keys))
for i, _ := range keys {
intKeys[i] = keys[i]
}
c := rc.conn.Get()
entries := make(map[string]interface{})
values, err := redis.Strings(c.Do("MGET", intKeys...))
if err != nil {
fmt.Println(err)
return entries
}
for i, k := range keys {
entries[k] = values[i]
}
return entries
}
客户端在使用Cache时,是直接用Cache接口中定义的方法跟适配器交互,由适配器再去转换成对三方依赖库redigo
的调用完成Redis操作。
func main() {
var rc Cache
rc = NewRedisCache()
rc.Put("网管叨逼叨", "rub fish")
}
适配器和代理模式的区别:
适配器模式的优点是适配器类和原角色类解耦,提高程序的扩展性。在很多业务场景中符合开闭原则。不改变原有接口,却还能使用新接口的功能。不过适配器的编写过程需要结合业务场景全面考虑,同时也可能会增加系统的复杂性。
参考公众号网管叨bi叨