结构型设计模式—适配器模式

2024-01-02 16:27:19

适配器模式:使用“适配器”,将原本原本由于接口不兼容而不能一起工作的那些类可以在一起工作

适配器模式中的角色构成如下:

  1. 客户端(Client):客户端可以理解成通过适配器调用服务的代码程序,代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。
  2. 客户端接口(Client Interface):接口也可被叫做适配器接口,描述了被适配的类与客户端代码协作时必须遵循的约定。
  3. 适配器 (Adapter): 作为同时与客户端和服务交互的中介类: 它在实现客户端接口的同时封装了服务对象。 适配器接受客户端通过适配器接口发起的调用, 并将其转换为适用于被封装服务对象的调用。
  4. 服务(Service):服务通常是一些第三方功能类库或者是一些遗留系统的功能类,客户端与其不兼容,因此无法直接调用其功能,需要适配器进行转换。

客户端代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。如果有需求可以向程序中添加新类型的适配器而无需修改已有适配器实现。

//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")
}

适配器和代理模式的区别:

  1. 适配器与原对象(被适配对象)实现不同的接口,适配器的特点在于兼容,客户端通过适配器的接口完成跟自己不兼容的原对象的访问交互。
  2. 代理与原对象(被代理对象)实现相同的接口,代理模式的特点在于隔离和控制,代理直接转发原对象的返回给客户端,但是可以在调用原始对象接口的前后做一些额外的辅助工作,AOP编程的实现也是利用这个原理。

适配器模式的优点是适配器类和原角色类解耦,提高程序的扩展性。在很多业务场景中符合开闭原则。不改变原有接口,却还能使用新接口的功能。不过适配器的编写过程需要结合业务场景全面考虑,同时也可能会增加系统的复杂性。
参考公众号网管叨bi叨

文章来源:https://blog.csdn.net/zhaicheng55/article/details/135341099
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。