当前位置: 首页 > 科技观察

Go设计模式|项目依赖耦合度太高?可以使用适配器进行优化

时间:2023-03-14 00:08:16 科技观察

大家好,这里是每周陪伴大家的网管~!今天给大家介绍一种我们在开发项目时经常用到的设计模式——适配器模式。适配器模式(AdapterPattern)也称为转换器模式。它的作用是将一个类的接口改变为客户所期望的另一个接口,使得因接口不匹配而不能协同工作的两个类能够协同工作,是一种结构设计模式。适配器模式使那些由于接口不兼容而无法一起工作的类能够一起工作。让我们用UML类图来看一下适配器模式的结构。适配器模式的结构类图——适配器模式的结构。适配器模式中的角色如下:调用服务的代码程序,代码只需要通过接口与适配器进行交互,不需要与具体的适配器类耦合。客户端接口(ClientInterface):该接口也可以称为适配器接口,它描述了适配类与客户端代码协作时必须遵循的契约。适配器(Adapter):作为与客户端和服务交互的中间类:它在实现客户端接口的同时封装了服务对象。适配器通过适配器接口接受客户端发起的调用,并将其转换为适合封装服务对象的调用。服务(Service):服务通常是一些第三方的功能库或者一些遗留系统的功能类。客户端不兼容它们,所以不能直接调用它们的函数,需要适配器进行转换。通过上面类图中各个角色类的关联,我们可以看出,客户端代码只需要通过接口与适配器交互即可,不需要与具体的适配器类耦合。这样,如果有需要,我们可以在不修改现有适配器实现的情况下,为程序添加新类型的适配器。这在我们的项目需要更换服务类时很有用,符合SOLID原则中的开闭原则。我们先来看看如何用代码实现适配器模式,稍后再给大家看一个更实际的例子。//目标适配接口,描述客户端与适配服务约定的接口。typeTargetinterface{Request()string}//Adaptee是适配的目标接口typeAdapteeinterface{SpecificRequest()string}//NewAdaptee是工厂函数funcNewAdaptee()Adaptee{return&adapteeImpl{}}//AdapteeImpl是适配的interfaceAdaptedtargetclasstypeadapteeImplstruct{}//SpecificRequest是目标类的一个方法func(*adapteeImpl)SpecificRequest()string{return"adapteemethod"}//NewAdapter是Adapter的工厂函数funcNewAdapter(adapteeAdaptee)Target{return&adapter{Adaptee:adaptee,}}//Adapter是一个适配器,将Adaptee转换为Target接口类型adapterstruct{Adaptee}//Request实现Target接口func(a*adapter)Request()string{returna.SpecificRequest()}客户端代码e直接通过适配器间接使用适配对象的功能,解决了两者不兼容的问题。"本文使用的完整可运行源码可以发到公众号"网络管理备忘[设计模式]获取"import"testing"varexpect="adapteemethod"funcTestAdapter(t*testing.T){adaptee:=NewAdaptee()target:=NewAdapter(adaptee)res:=target.Request()ifres!=expect{t.Fatalf("expect:%s,actual:%s",expect,res)}}使用适配器模式引入三方依赖为什么在引入依赖库时推荐使用适配器模式?当项目使用第三方类库时,一般建议使用适配器模式对第三方类库进行封装,以防止以后替换相同功能类库的可能性。当以后需要替换相同功能的服务类时,可以在不改变现有客户端代码的情况下,实现一个新的适配器封装服务类。使用适配器方式访问项目中的依赖库,这样你需要在替换其他功能相当的依赖库时,不会影响项目中通过适配器使用依赖库函数的代码。下面是一个使用adapter适配redigo库为项目提供RedisCache功能的例子。首先我们定义了适配器接口,以后所有Cache类的适配器都需要实现这个接口。import(..."github.com/gomodule/redigo/redis")//Cache定义适配器实现类要实现的接口typeCacheinterface{Put(keystring,valueinterface{})Get(keystring)interface{}GetAll(keys[]string)map[string]interface{}}为了简洁起见,这里只定义了三个访问Cache的简单方法。在实际使用中,我们可以将常用的Cache操作定义为接口的方法。定义适配器实现类。RedisCache类型将有一个Cache接口。同时,我们为RedisCache提供了一个工厂方法,在工厂方法中初始化Redis连接池。//RedisCache实现适配器接口。"发送"设计模式"接收"typeRedisCachestruct{conn*redis.Pool}//RedisCache工厂方法funcNewRedisCache()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")iferr!=nil{fmt.Println(err)returnnil,err}if_,err:=conn.Do("SELECT",0);err!=nil{conn.Close()fmt.Println(err)returnnil,err}returnconn,nil},},}returncache}接下来实现RedisCache的Cache适配器接口的方法。这三个方法分别对应Redis的SET、GET、MGET操作。"本文用到的完整可运行源码可以发到公众号"网络管理备忘[设计模式]获取"//缓存数据func(rc*RedisCache)Put(keystring,valueinterface{}){if_,err:=rc.conn.Get().Do("SET",key,value);err!=nil{fmt.Println(err)}}//获取缓存中指定Key的值func(rc*RedisCache)Get(keystring)interface{}{value,err:=redis.String(rc.conn.Get().Do("GET",key))iferr!=nil{fmt.Println(err)return""}returnvalue}//从缓存中获取多个Key值func(rc*RedisCache)GetAll(keys[]string)map[string]interface{}{intKeys:=make([]interface{},len(keys))fori,_:=rangekeys{intKeys[i]=keys[i}]}c:=rc.conn.Get()entries:=make(map[string]interface{})values,err:=redis.Strings(c.Do("MGET",intKeys...))如果出错!=nil{fmt.Println(err)returnentries}fori,k:=rangekeys{entries[k]=values[i]}returnentries}客户端使用Cache时,直接使用Cache中定义的方法接口与适配器进行交互,适配器将其转化为调用三方依赖库redigo,完成对Redis的操作。funcmain(){varrcCacherc=NewRedisCache()rc.Put("NetworkManagement","rubfish")}本文完整源码已收录在我整理的电子教程中。可以发给我公众号“网管唠叨bi唠叨”发送关键词【设计模式】接收。公众号「网管唠叨bi唠叨」发关键词【设计模式】收藏。适配器模式和代理模式的区别适配器模式和代理模式都属于结构设计模式,它们在类结构上也很相似,都是由一个包装对象持有,将客户端对包装对象的请求转发给原始对象。那么这两种模式有什么区别呢?我们如何区分我们使用的是适配器模式还是代理模式呢?适配器和代理模式的区别:适配器和原对象(适配对象)实现的接口不同。适配器具有兼容性的特点,客户端通过适配器的接口完成与不兼容的原始对象的访问交互。代理实现与原始对象(被代理对象)相同的接口。代理模式的特点是隔离和控制。代理直接将原对象的返回转发给客户端,但可以在调用原对象接口前后做一些额外的辅助工作。AOP编程的实现也是利用了这个原理。总结适配器模式的好处是适配器类与原来的角色类解耦,提高了程序的可扩展性。在很多业务场景中,它符合开闭原则。无需改动原有界面即可使用新界面的功能。但是适配器的编写过程需要结合业务场景充分考虑,也可能增加系统的复杂度。今天的文章就这些了,喜欢的话请关注,每周更新最实用的编程知识。