Go语言实现GoF设计模式:适配器模式

简介适配器模式(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 的接口实现 。
Go语言实现GoF设计模式:适配器模式

文章插图
 
UML 结构
Go语言实现GoF设计模式:适配器模式

文章插图
 
场景上下文在 简单的分布式应用系统(示例代码工程File not found · GitHub)中 , db 模块用来存储服务注册信息和系统监控数据 , 它是一个 key-value 数据库 。在 访问者模式 中,我们为它实现了 Table 的按列查询功能;同时,我们也为它实现了简单的 SQL 查询功能(将会在 解释器模式 中介绍),查询的结果是 SqlResult 结构体 , 它提供一个 toMap 方法将结果转换成 map  。
为了方便用户使用 , 我们将实现在终端控制台上提供人机交互的能力 , 如下所示 , 用户输入 SQL 语句,后台返回查询结果:
Go语言实现GoF设计模式:适配器模式

文章插图
 
终端控制台的具体实现为 Console,为了提供可扩展的查询结果显示样式,我们设计了 ConsoleRender 接口,但因 SqlResult 并未实现该接口,所以 Console 无法直接渲染 SqlResult 的查询结果 。
Go语言实现GoF设计模式:适配器模式

文章插图
 
为此,我们需要实现一个适配器,让 Console 能够通过适配器将 SqlResult 的查询结果渲染出来 。示例中 , 我们设计了适配器 TableRender,它实现了 ConsoleRender 接口,并以表格的形式渲染出查询结果,如前文所示 。
Go语言实现GoF设计模式:适配器模式

文章插图
 
代码实现// demo/db/sql.gopackage db// Adaptee SQL语句执行返回的结果,并未实现Target接口type SqlResult struct {fields []stringvals[]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 []stringvar data []stringfor key, val := range vals {header = append(header, key)data = https://www.isolves.com/it/cxkf/yy/go/2023-12-12/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("> ")}}


推荐阅读