上一篇:【go实现】实践gof的23种设计模式:备忘录模式
简单的分布式应用系统(示例代码工程):https://github.com/ruanrunxue/practice-design-pattern--go-implementation
简介 适配器模式(adapter)是最常用的结构型模式之一,在现实生活中,适配器模式也是处处可见,比如电源插头转换器,它可以让英式的插头工作在中式的插座上。
gof 对它的定义如下:
convert the interface of a class into another interface clients expect. adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
简单来说,就是适配器模式让原本因为接口不匹配而无法一起工作的两个类/结构体能够一起工作。
适配器模式所做的就是将一个接口 adaptee,通过适配器 adapter 转换成 client 所期望的另一个接口 target 来使用,实现原理也很简单,就是 adapter 通过实现 target接口,并在对应的方法中调用 adaptee 的接口实现。
uml 结构 场景上下文 在 简单的分布式应用系统(示例代码工程)中,db 模块用来存储服务注册信息和系统监控数据,它是一个 key-value 数据库。在 访问者模式 中,我们为它实现了 table 的按列查询功能;同时,我们也为它实现了简单的 sql 查询功能(将会在 解释器模式 中介绍),查询的结果是 sqlresult 结构体,它提供一个 tomap 方法将结果转换成 map 。
为了方便用户使用,我们将实现在终端控制台上提供人机交互的能力,如下所示,用户输入 sql 语句,后台返回查询结果:
终端控制台的具体实现为 console,为了提供可扩展的查询结果显示样式,我们设计了 consolerender 接口,但因 sqlresult 并未实现该接口,所以 console 无法直接渲染 sqlresult 的查询结果。
为此,我们需要实现一个适配器,让 console 能够通过适配器将 sqlresult 的查询结果渲染出来。示例中,我们设计了适配器 tablerender,它实现了 consolerender 接口,并以表格的形式渲染出查询结果,如前文所示。
代码实现 // demo/db/sql.gopackage db// adaptee sql语句执行返回的结果,并未实现target接口type sqlresult struct { fields []string vals []interface{}}func (s *sqlresult) add(field string, record interface{}) { s.fields = append(s.fields, field) s.vals = append(s.vals, record)}func (s *sqlresult) tomap() map[string]interface{} { results := make(map[string]interface{}) for i, f := range s.fields { results[f] = s.vals[i] } return results}// demo/db/console.gopackage db// client 终端控制台type console struct { db db}// output 调用consolerender完成对查询结果的渲染输出func (c *console) output(render consolerender) { fmt.println(render.render())}// target接口,控制台db查询结果渲染接口type consolerender interface { render() string}// tablerender表格形式的查询结果渲染adapter// 关键点1: 定义adapter结构体/类type tablerender struct { // 关键点2: 在adapter中聚合adaptee,这里是把sqlresult作为tablerender的成员变量 result *sqlresult}// 关键点3: 实现target接口,这里是实现了consolerender接口func (t *tablerender) render() string { // 关键点4: 在target接口实现中,调用adaptee的原有方法实现具体的业务逻辑 vals := t.result.tomap() var header []string var data []string for key, val := range vals { header = append(header, key) data = append(data, fmt.sprintf(%v, val)) } builder := &strings.builder{} table := tablewriter.newwriter(builder) table.setheader(header) table.append(data) table.render() return builder.string()}// 这里是另一个adapter,实现了将error渲染的功能type errorrender struct { err error}func (e *errorrender) render() string { return e.err.error()} 客户端这么使用:
func (c *console) start() { fmt.println(welcome to demo db, enter exit to end!) fmt.println(> please enter a sql expression:) fmt.print(> ) scanner := bufio.newscanner(os.stdin) for scanner.scan() { sql := scanner.text() if sql == exit { break } result, err := c.db.execsql(sql) if err == nil { // 关键点5:在需要target接口的地方,传入适配器adapter实例,其中创建adapter实例时需要传入adaptee实例 c.output(newtablerender(result)) } else { c.output(newerrorrender(err)) } fmt.println(> please enter a sql expression:) fmt.print(> ) }} 在已经有了 target 接口(consolerender)和 adaptee(sqlresult)的前提下,总结实现适配器模式的几个关键点:
定义 adapter 结构体/类,这里是 tablerender 结构体。 在 adapter 中聚合 adaptee,这里是把 sqlresult 作为 tablerender 的成员变量。 adapter 实现 target 接口,这里是 tablerender 实现了 consolerender 接口。 在 target 接口实现中,调用 adaptee 的原有方法实现具体的业务逻辑,这里是在 tablerender.render() 调用 sqlresult.tomap() 方法,得到查询结果,然后再对结果进行渲染。 在 client 需要 target 接口的地方,传入适配器 adapter 实例,其中创建 adapter 实例时传入 adaptee 实例。这里是在 newtablerender() 创建 tablerender 实例时,传入 sqlresult 作为入参,随后将 tablerender 实例传入 console.output() 方法。 扩展 适配器模式在 gin 中的运用 gin 是一个高性能的 web 框架,它的常见用法如下:
// 用户自定义的请求处理函数,类型为gin.handlerfuncfunc myginhandler(c *gin.context) { ... // 具体处理请求的逻辑}func main() { // 创建默认的route引擎,类型为gin.engine r := gin.default() // route定义 r.get(/my-route, myginhandler) // route引擎启动 r.run()} 在实际运用场景中,可能存在这种情况。用户起初的 web 框架使用了 go 原生的 net/http,使用场景如下:
// 用户自定义的请求处理函数,类型为http.handlerfunc myhttphandler(w http.responsewriter, r *http.request) { ... // 具体处理请求的逻辑}func main() { // route定义 http.handlefunc(/my-route, myhttphandler) // route启动 http.listenandserve(:8080, nil)} 因性能问题,当前客户准备切换至 gin 框架,显然,myhttphandler 因接口不兼容,不能直接注册到 gin.default() 上。为了方便用户,gin 框架提供了一个适配器 gin.wraph,可以将 http.handler 类型转换成 gin.handlerfunc 类型,它的定义如下:
// wraph is a helper function for wrapping http.handler and returns a gin middleware.func wraph(h http.handler) handlerfunc { return func(c *context) { h.servehttp(c.writer, c.request) }} 使用方法如下:
// 用户自定义的请求处理函数,类型为http.handlerfunc myhttphandler(w http.responsewriter, r *http.request) { ... // 具体处理请求的逻辑}func main() { // 创建默认的route引擎 r := gin.default() // route定义 r.get(/my-route, gin.wraph(myhttphandler)) // route引擎启动 r.run()} 在这个例子中,gin.engine 就是 client,gin.handlerfunc 是 target 接口,http.handler 是 adaptee,gin.wraph 是 adapter。这是一个 go 风格的适配器模式实现,以更为简洁的 func 替代了 struct。
典型应用场景 将一个接口 a 转换成用户希望的另外一个接口 b,这样就能使原来不兼容的接口 a 和接口 b 相互协作。 老系统的重构。在不改变原有接口的情况下,让老接口适配到新的接口。 优缺点 优点 能够使 adaptee 和 target 之间解耦。通过引入新的 adapter 来适配 target,adaptee 无须修改,符合开闭原则。 灵活性好,能够很方便地通过不同的适配器来适配不同的接口。 缺点 增加代码复杂度。适配器模式需要新增适配器,如果滥用会导致系统的代码复杂度增大。 与其他模式的关联 适配器模式 和 装饰者模式、代理模式 在 uml 结构上具有一定的相似性。但适配器模式改变原有对象的接口,但不改变原有功能;而装饰者模式和代理模式则在不改变接口的情况下,增强原有对象的功能。
文章配图 可以在 用keynote画出手绘风格的配图 中找到文章的绘图方法。
解析戴姆勒案背后的物联网专利许可争议
一个简单易于制作的电源电路图
测量晶体三极管的新方法
深圳成立机器人产业基地 进一步推进机器人产业发展
将DS1859与外部EEPROM配合使用
实践GoF的23种设计模式:适配器模式
狼来了的故事!还要讲多久?智能汽车时代的贾跃亭
小米屏下指纹手机可用APP锁定前置摄像头
煤矿瓦斯全作业链在线监测与预警物联网解决方案
欧盟将锂电池视为电动车领域的核心技术,重新审视锂电池产业
混频器和变频器的区别
基于KK2.1.5设计的X形四轴飞行器
华为66W移动电源首销:Mate 40/笔记本必备神器
LTM9002-AA-14位、双通道IF/基带接收器子系统
STI3508CB升压转换器概述、特征及应用
Han-INOX®不锈钢外壳可对腐蚀和侵蚀性物质进行防护
为您的供应链增加信任
基于亚马逊无人机送货系统
u-blox与英伟达无人驾驶生态系统达成合作伙伴关系
星河亮点推出5G毫米波综测仪原型系统,符合3GPP标准规范要求