设计模式,解决N家资源方接入

GO语言+设计模式

场景

我们的业务是医疗场景下的问诊业务,具体场景包括在线咨询和线上复诊,每个场景都分为免费和付费,也就是一共 4 个业务流程,并且相互之间有很大一部分流程相同,但又在部分流程中有或多或少的区别。同时,需要对接医院 HIS 系统的挂号服务,并且每家医院的挂号流程均不相同,比如有些需要手动开收费项来收取挂号诊查费,有些是免费。

方案

系统需要同时支持 4 种业务流程,且挂号兼容多家 HIS 系统

·接口抽象标准流程,实现 4 种业务流程。
·抽象与实现解耦,两者可独立变化,互不影响。
·实现与 HIS 系统解耦,多家 HIS 接入无需改动流程代码
既然谈到抽象和解耦,就一定离不开设计模式,要想实现上面的几点,需要用到桥接模式、工厂模式、适配器模式、单例模式。

代码抽象

主要抽象流程如下:
·handler.go:桥接模式,组合问诊标准流程,组合 4 种业务流程和 N 家 HIS 挂号逻辑
·inquiry.go:问诊 4 种业务流程抽象解耦
·inquiry_factory.go:问诊工厂 + 单例
·his_one.go(his_two.go):HIS适配器
·his_factory.go:HIS 工厂+ 单例
如图

桥接模式

通过 HandlerInterface 定义每个请求的标准流程,下面以创建问诊订单为例。整个创建订单需要经过 5 个步骤:锁号、挂号、创建支付订单、创建问诊订单、创建聊天会话。
在桥接模式的标准实现中,是将不同的接口作为类成员来达到组合的目的,但是在当前业务中并不适用,因为我们需要动态地根据不同参数来执行 4 种不同的处理流程。所以我们在标准模式上进行了一些调整,通过参数注入的方式,将 Inquiry 和 His 接口注入,实现问诊和 HIS 的灵活组合,将业务流程和不同实现灵活组合,达到三者之间的解耦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package inquiry_abstract
import "context"
// HandlerInterface 标准流程抽象Handler
type HandlerInterface interface {
// CreateOrder 创建订单
CreateOrder(ctx context.Context, inquiry InquiryInterface, his HisInterface, orderCode string) (err error)
}
type Handler struct{}
func (*Handler) CreateOrder(ctx context.Context, inquiry InquiryInterface, his HisInterface, orderCode string) (err error) {
inquiry.lockRegister(ctx)
his.register(ctx, orderCode)
inquiry.createOrder(ctx)
inquiry.createInquiryOrder(ctx)
inquiry.createDialog(ctx)
return
}

适配器

整个系统需要支持 N 个不同的 HIS 系统接入,主要是为了兼容不同医院的挂号流程,从而将主流程和 HIS 之间的交互解耦。后续医院接入只需要增加对应的适配器即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package inquiry_abstract
import (
"context"
"fmt"
)
// hisOneAdapter hisOne适配器
type hisOneAdapter struct {
his hisOne
}
func (h *hisOneAdapter) register(ctx context.Context, orderCode string) (err error) {
h.his.register(ctx, orderCode)
return
}
// hisOne hisOne逻辑
type hisOne struct{}
func (*hisOne) register(ctx context.Context, orderCode string) (err error) {
fmt.Println("hisA register")
return
}
// hisTwoAdapter hisTwo适配器
type hisTwoAdapter struct {
his hisTwo
}
func (h *hisTwoAdapter) register(ctx context.Context, _orderCode string) (err error) {
orderCode, _ := strconv.Atoi(_orderCode)
h.his.register(ctx, orderCode)
return
}
// hisTwo hisTwo逻辑
type hisTwo struct{}
func (*hisTwo) register(ctx context.Context, orderCode int) (err error) {
fmt.Println("hisB register")
return
}

如代码所示,hisOne 接收的就诊订单编码为 string,hisTwo 接收的就诊订单编码为 int(这里只是举个例子,真实的业务场景远比这个复杂),我们通过 HisInterface 对适配器进行抽象,上游业务通过接口进行调用,然后两家医院定义两个适配器,将订单编码转化隐藏到适配器实现,调用方永远只需要传入 string 即可。

工厂模式

工厂模式比较简单,就不赘述了,这里主要用在两个地方:

·根据不同参数,分别返回咨询、免费咨询、复诊、免费复诊,四个不同的流程实现。
·根据不同的医院,分别返回对应的 HIS 适配器。

两种业务两种付费方式的工厂
inquiry_factory.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package inquiry_abstract
import (
"context"
"errors"
)
const (
TypeConsultation uint8 = 1
TypeDiagnose uint8 = 1
)
var (
ErrType = errors.New("type error")
)
var (
Consultation InquiryInterface
FreeConsultation InquiryInterface
Diagnose InquiryInterface
FreeDiagnose InquiryInterface
)
// init Inquiry单例
func init() {
Consultation = &consultation{}
FreeConsultation = &freeConsultation{}
Diagnose = &diagnose{}
FreeDiagnose = &freeDiagnose{}
}
// NewInquiry Inquiry工厂
func NewInquiry(ctx context.Context, typ uint8, price int) (obj InquiryInterface, err error) {
obj = Consultation
if typ == TypeConsultation && price == 0 {
obj = FreeConsultation
return
}
if typ == TypeDiagnose && price == 0 {
obj = FreeDiagnose
return
}
if typ == TypeDiagnose && price > 0 {
obj = Diagnose
return
}
err = ErrType
return
}

对接不同医院的工厂
his_factory.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package inquiry_abstract
import (
"context"
"errors"
)
const (
HisCodeOne = "one"
HisCodeTwo = "two"
)
var (
ErrHIS = errors.New("his error")
)
var (
HisOne HisInterface
HisTwo HisInterface
)
// init HIS单例
func init() {
HisOne = &hisOneAdapter{}
HisTwo = &hisTwoAdapter{}
}
// NewHis HIS工厂
func NewHis(ctx context.Context, hisCode string) (his HisInterface, err error) {
switch hisCode {
case HisCodeOne:
his = HisOne
case HisCodeTwo:
his = HisTwo
default:
err = ErrHIS
}
return
}

单列模式

单例模式更是老生常谈,常见的有饿汉和懒汉,面试时经常让我们手写,这里也不再赘述。我们采用了饿汉实现,通过 init 在导入时进行资源初始化,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var (
Consultation InquiryInterface
FreeConsultation InquiryInterface
Diagnose InquiryInterface
FreeDiagnose InquiryInterface
)
// init Inquiry单例
func init() {
Consultation = &consultation{}
FreeConsultation = &freeConsultation{}
Diagnose = &diagnose{}
FreeDiagnose = &freeDiagnose{}
}

1
2
3
4
5
6
7
8
9
10
var (
HisOne HisInterface
HisTwo HisInterface
)
// init HIS单例
func init() {
HisOne = &hisOneAdapter{}
HisTwo = &hisTwoAdapter{}
}

总结

通过抽象,配合设计模式,在后续的医院对接的过程将会降低成本,提升开发效率。

原文:https://success.blog.csdn.net/article/details/119486418
Github:https://github.com/why444216978/inquiry_abstract