精通 Golang 适配器模式
示例代码已经上传到我的 Github: https://github.com/kaolengmian7/golang-demo/tree/master/design_pattern/adapter ,可以把代码拉下来跑一跑~
核心思想
适配器是两个不兼容的接口之间的桥梁,使接口不兼容的对象能够相互合作。
使用场景
有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
- 封装有缺陷的接口
- 封装多个接口
- 替换依赖
- 兼容老版本接口
- 适配不同格式数据
一定要注意:适配器为的是解决正在服役的项目的问题,而不是在设计阶段添加的。如果在设计阶段就考虑使用适配器模式,那这个设计一定不合格。
实战
假如你正在开发一款股票市场监测程序, 它会从不同来源下载 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")
}
你可以修改函数库来支持 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
}
参考: