精通 Golang 适配器模式

示例代码已经上传到我的 Github: https://github.com/kaolengmian7/golang-demo/tree/master/design_pattern/adapter ,可以把代码拉下来跑一跑~

核心思想

适配器是两个不兼容的接口之间的桥梁,使接口不兼容的对象能够相互合作。

使用场景

有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。

  1. 封装有缺陷的接口
  2. 封装多个接口
  3. 替换依赖
  4. 兼容老版本接口
  5. 适配不同格式数据

一定要注意:适配器为的是解决正在服役的项目的问题,而不是在设计阶段添加的。如果在设计阶段就考虑使用适配器模式,那这个设计一定不合格。

实战

假如你正在开发一款股票市场监测程序, 它会从不同来源下载 XML 格式的股票数据, 然后向用户呈现出美观的图表。

type XmlHandler interface {
	ProcessXml(xml string)
}

type xmlHandler struct{}

func (x *xmlHandler) ProcessXml(xml string) {
	log.Printf("xml:%s processing", xml)
}

在开发过程中, 你决定在程序中整合一个第三方智能分析函数库。 但是遇到了一个问题, 那就是分析函数库只兼容 JSON 格式的数据。

// 分析库接口
type JsonHandler interface {
	ProcessJson(json []byte)
}

type jsonHandler struct {
}

func (j *jsonHandler) ProcessJson(json []byte) {
	log.Printf("json processing")
}

image.png 你可以修改函数库来支持 XML。 但是, 这可能需要大量代码。 或者,我们可以将 XML 转换成 JSON?

xml 与 json 代表了现实中不兼容的两个接口。 为了演示方便,我用string类型表示xml文件,用[]byte表示json文件

适配器就是为两个兼容两个接口而生,在此例中:xmlAdapter继承了XmlHandler接口,其中封装了对接口的处理。结果就是,客户端的 JSON 处理被“适配”成 XML 处理。

type xmlAdapter struct {
	JsonHandler
}

func (x *xmlAdapter) ProcessXml(xml string) {
	// 模拟对象转换:xml -> json
	json := []byte(xml)
	// 处理 json 文件
	x.ProcessJson(json)
}

func TestAdapter(t *testing.T) {
	inputJson := []byte("json_file")
	// 原客户端
	handler := &jsonHandler{}
	handler.ProcessJson(inputJson)

	// 客户端接入适配器,利用 原有的 JsonHandler 处理 xml 文件。
	inputXml := "xml_file"
	adapter := &xmlAdapter{&jsonHandler{}}
	adapter.ProcessXml(inputXml)
}

优点

灵活性强,上线新功能不需要改动大量源代码,仅需要在接口层做一层类型转换。

缺点

过多地使用适配器,会让系统非常零乱。比如,表明上看到调用的是 A 接口,其实内部被适配成了 B 接口的实现。 一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,尽量不使用适配器,而是直接对系统进行重构。

更进一步:加强版适配器

对于 Golang 这种可以实现多继承的语言来讲,适配器有更优雅的解决方案:让适配器实现所有接口。

type adapter struct {
	jsonHandler JsonHandler
	xmlHandler  XmlHandler
}

这样的适配器强大的多,想用谁的实现就用谁的实现,与此同时完全规避了上面提到的缺点,不容易产生误解。

func TestAdapterInGolang(t *testing.T) {
	inputJson := []byte("json_file")
	inputXml := "xml_file"
	// 原客户端
	handler := &jsonHandler{}
	handler.ProcessJson(inputJson)

	// 客户端接入适配器
	adapter := &adapter{&jsonHandler{}, &xmlHandler{}}
	adapter.jsonHandler.ProcessJson(inputJson) // 想用 json 用 json
	adapter.xmlHandler.ProcessXml(inputXml)    // 想用 xml 用 xml
}

也可以重写函数:

type adapter struct {}

func (x *adapter) ProcessJson(json []byte) {
	// 自定义处理逻辑
}

func (x *adapter) ProcessXml(xml string) {
	// 自定义处理逻辑
}

func TestAdapterInGolang(t *testing.T) {
	inputJson := []byte("json_file")
	inputXml := "xml_file"
	// 原客户端
	handler := &jsonHandler{}
	handler.ProcessJson(inputJson)

	// 客户端接入适配器
	adapter := &adapter{&jsonHandler{}, &xmlHandler{}}
	adapter.ProcessJson(inputJson) // 想用 json 用 json
	adapter.ProcessXml(inputXml)    // 想用 xml 用 xml
}

参考:

  1. https://refactoringguru.cn/design-patterns/adapter
  2. http://shusheng007.top/2021/09/08/018/
  3. https://www.runoob.com/design-pattern/adapter-pattern.html
  4. https://lailin.xyz/post/adapter.html#comments