阅读 22

Go设计模式(14)-适配器模式

前面三篇文章讲了代理模式、桥接模式、装饰器模式,这次讲一下适配器模式。

适配器模式比较简单,也比较容易理解。适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。应用这种模式算是“无奈之举”。

UML类图位置:www.processon.com/diagraming/…

本文代码链接为:github.com/shidawuhen/…

1.定义

1.1适配器模式

适配器模式:将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

UML:

图片

1.2分析

通过UML图看到Adapter和Adaptee是关联关系,但Adapter和Adaptee也可以是继承关系,这种情况一般用于Adaptee大部分成员函数已经和Target一致,只有少部分需要修改,使用继承能够减少代码改动。如果Adaptee大部分成员函数和Target不一致,最好还是用组合,毕竟组合优于继承。当然对Go而言就无所谓了,反正只有组合没有继承,而且匿名组合能够直接复用组合对象的功能。

适配器模式的使用也比较简单,核心就是用Adapter重新封装一下Adaptee,使其符合Target的要求。

2.使用场景

遇到如下几种场景的时候可以考虑使用适配器模式:

  1. 封装有缺陷的接口设计:例如如果引入的外部系统接口设计方面有缺陷,会影响我们自身代码的可测性等,就可以考虑使用适配器模式,将引入的系统向我们自身系统设计上靠拢

  2. 统一多个类的接口设计:如果一个功能依赖多个外部系统,且这些外部系统的能力是相似的但接口不统一,可以使用适配器模式,依赖于继承、多态的特性,使调用方可以以聚合方式使用外部系统,提升代码扩展性

  3. 替换依赖的外部系统:如果一个功能有多个外部系统可供选择,我们可以定义一个Target接口,将外部系统适配为Target,这样就能利用多态性,实现外部系统的替换

  4. 兼容老版本接口:老版本中功能A在新版本中被废弃,A将由B替代,为了不影响使用者,新版本中仍然会有A,但是其内部实现委托B执行

  5. 适配不同格式的数据:有时数据来源不同,数据格式也不同,需要对数据做适配,改为统一格式后再处理,也可使用适配器模式

我在项目中用适配器模式,主要处理数据适配。以前做支付网关的时候,每个第三方返回的数据都不一致,为了符合支付系统定义的格式,必须进行适配。这种地方特别多,就选对账功能这块讲一下,其它关于支付的内容可以参考这篇文章如何高效对接第三方支付

3.代码实现

所谓对账,是指从第三方支付公司拉取指定时间内的支付单信息,与系统内部支付单信息做对比,主要用来发现支付异常

1.支付网关有数据,第三方没有数据

-可能被黑客攻击了,用户没有真正支付,但是我们发货了

-代码有问题,用户没有完成支付,但是系统认为支付成功了

-第三方提供数据不全

2.支付网关没有数据,第三方有数据

-用户支付成功,但是同步或者异步通知都失败了

3.金额不一致

-代码有问题,电商发起支付金额和真正调用第三方金额不一致

-第三方提供数据有问题

做对比的逻辑是一致的,但是第三方支付账单数据格式不一致,所以需要先将这些数据转化为标准格式。

package main

import (
	"fmt"
	"time"
)

/**
 * @Author: Jason Pang
 * @Description: 对账单数据
 */
type StatementItem struct {
	OrderId       string //系统单号
	TransactionId string //第三方交易号
	Amount        int64  //支付金额,单位:分
	PaymentTime   int64  //订单支付时间
}

/**
 * @Author: Jason Pang
 * @Description: 从第三方获取对账数据
 */
type StatementData interface {
	GetStatementData(startTime int64, endTime int64) []*StatementItem
}

/**
 * @Author: Jason Pang
 * @Description: WX支付
 */
type WXStatementData struct {
}

func (w *WXStatementData) GetStatementData(startTime int64, endTime int64) []*StatementItem {
	fmt.Println("从WX获取到的对账数据,支付时间需要格式化为时间戳")
	return []*StatementItem{
		{
			OrderId:       "WX订单222",
			TransactionId: "WX支付单号",
			Amount:        999,
			PaymentTime:   time.Date(2014, 1, 7, 5, 50, 4, 0, time.Local).Unix(),
		},
	}
}

/**
 * @Author: Jason Pang
 * @Description: ZFB支付
 */
type ZFBStatementData struct {
}

func (z *ZFBStatementData) GetStatementData(startTime int64, endTime int64) []*StatementItem {
	fmt.Println("从ZFB获取到的对账数据,金额需要从元转化为分")
	return []*StatementItem{
		{
			OrderId:       "ZFB订单111",
			TransactionId: "ZFB支付单号",
			Amount:        99.9 * 100,
			PaymentTime:   1389058332,
		},
	}
}

/**
 * @Author: Jason Pang
 * @Description: 对账函数
 * @param list  从第三方获取的对账单
 * @return bool
 */
func DoStatement(list []*StatementItem) bool {
	fmt.Println("开始对账")
	fmt.Println("从自身系统中获取指定时间内的支付单")
	for _, item := range list {
		fmt.Println(item.OrderId + " 与系统支付单进行对账")
	}
	fmt.Println("对账完成")
	return true
}

func main() {
	wx := &WXStatementData{}
	zfb := &ZFBStatementData{}
	stattementData := []StatementData{
		wx,
		zfb,
	}
	for _, s := range stattementData {
		DoStatement(s.GetStatementData(1389058332, 1389098332))
	}
}

复制代码

输出:

➜ myproject go run main.go

从WX获取到的对账数据,支付时间需要格式化为时间戳

开始对账

从自身系统中获取指定时间内的支付单

WX订单222 与系统支付单进行对账

对账完成

从ZFB获取到的对账数据,金额需要从元转化为分

开始对账

从自身系统中获取指定时间内的支付单

ZFB订单111 与系统支付单进行对账

对账完成

PS:代码中定义了对账单的结构,今后对接新的支付方式,只要实现了StatementData接口,就能参与对账,扩展性极好。

总结

适配器模式简单好用,用对了场景能够极大提高扩展性和优雅性。

适配器模式和代理、装饰器、桥接模式有一定相似性,我们在此处也总结一下:

代理模式: 代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

装饰器模式: 装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

适配器模式: 适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

桥接模式: 桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

我的个人博客为:shidawuhen.github.io/

往期文章回顾:

招聘

  1. 字节跳动|内推大放送

  2. 字节跳动|今日头条广州服务端研发工程师内推

  3. 字节跳动|抖音电商急招上海前端开发工程

  4. 字节跳动|抖音电商上海资深服务端开发工程师-交易

  5. 字节跳动|抖音电商武汉服务端(高级)开发工程师

  6. 字节跳动|飞书大客户产品经理内推咯

  7. 字节跳动|抖音电商服务端技术岗位虚位以待

  8. 字节跳动招聘专题

设计模式

  1. Go设计模式(13)-装饰器模式

  2. Go设计模式(12)-桥接模式

  3. Go设计模式(11)-代理模式

  4. Go设计模式(10)-原型模式

  5. Go设计模式(9)-建造者模式

  6. Go设计模式(8)-抽象工厂

  7. Go设计模式(7)-工厂模式

  8. Go设计模式(6)-单例模式

  9. Go设计模式(5)-类图符号表示法

  10. Go设计模式(4)-代码编写优化

  11. Go设计模式(4)-代码编写

  12. Go设计模式(3)-设计原则

  13. Go设计模式(2)-面向对象分析与设计

  14. Go设计模式(1)-语法

语言

  1. 再也不怕获取不到Gin请求数据了

  2. 一文搞懂pprof

  3. Go工具之generate

  4. Go单例实现方案

  5. Go通道实现原理

  6. Go定时器实现原理

  7. Beego框架使用

  8. Golang源码BUG追查

  9. Gin框架简洁版

  10. Gin源码剖析

架构

  1. 分页复选设计的坑

  2. 支付接入常规问题

  3. 限流实现2

  4. 秒杀系统

  5. 分布式系统与一致性协议

  6. 微服务之服务框架和注册中心

  7. 浅谈微服务

  8. 限流实现1

  9. CDN请求过程详解

  10. 常用缓存技巧

  11. 如何高效对接第三方支付

  12. 算法总结

存储

  1. MySQL开发规范

  2. Redis实现分布式锁

  3. 事务原子性、一致性、持久性的实现原理

  4. InnoDB锁与事务简析

网络

  1. HTTP2.0基础教程

  2. HTTPS配置实战

  3. HTTPS连接过程

  4. TCP性能优化

工具

  1. GoLand实用技巧

  2. 根据mysql表自动生成go struct

  3. Markdown编辑器推荐-typora

读书笔记

  1. 《毛选》推荐

  2. 原则

  3. 资治通鉴

  4. 敏捷革命

  5. 如何锻炼自己的记忆力

  6. 简单的逻辑学-读后感

  7. 热风-读后感

  8. 论语-读后感

  9. 孙子兵法-读后感

思考

  1. 为动员一切力量争取胜利而斗争

  2. 反对自由主义

  3. 实践论

  4. 评价自己的标准

  5. 服务端团队假期值班方案

  6. 项目流程管理

  7. 对项目管理的一些看法

  8. 对产品经理的一些思考

  9. 关于程序员职业发展的思考

  10. 关于代码review的思考

文章分类
后端