手撸golang 行为型设计模式 中介者模式
缘起
最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用golang练习之
中介者模式
中介者模式(Mediator Pattern)又叫作调解者模式或调停者模式。
用一个中介对象封装一系列对象交互,
中介者使各对象不需要显式地相互作用,
从而使其耦合松散,
而且可以独立地改变它们之间的交互,
属于行为型设计模式。
中介者模式主要适用于以下应用场景。
(1)系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解。
(2)交互的公共行为,如果需要改变行为,则可以增加新的中介者类。
(摘自 谭勇德 <<设计模式就该这样学>>)
场景
- 某物联网企业, 研发各种智能家居产品, 并配套手机app以便用户集中控制
- 一开始的设计是手机app通过本地局域网的广播协议, 主动发现/注册/控制各种智能设备
- 后来智能设备的种类越来越多, 通信协议多种多样, 导致手机app需要频繁升级, 集成过多驱动导致代码膨胀
- 研发部门痛定思痛, 决定采用中介者模式重新设计整个系统架构
- 老架构: app -> 智能设备*N
- 新架构: app -> 云中心 -> 智能设备
- 通过引入"云中心" 作为中介, 将app与设备驱动解耦
- app与云中心采用RESTFul协议通信, 极大提升开发运维的效率
设计
- MockPhoneApp: 虚拟的手机app, 用于跟云中心通信, 控制智能设备
- ICloudMediator: 云中心面向手机app的接口
- ICloudCenter: 云中心面向智能设备的注册接口
- ISmartDevice: 智能设备接口
- tMockCloudMediator: 虚拟的云中心服务类, 面向手机app实现ICloudMediator接口, 面向智能设备实现ICloudCenter接口
- tMockSmartLight: 虚拟的智能灯设备, 实现ISmartDevice接口
单元测试
mediator_pattern_test.go
package behavioral_patterns
import (
"learning/gooop/behavioral_patterns/mediator"
"testing"
)
func Test_MediatorPattern(t *testing.T) {
// 设备注册
center := mediator.DefaultCloudCenter
light := mediator.NewMockSmartLight(1)
center.Register(light)
fnCallAndLog := func(fn func() error) {
e := fn()
if e != nil {
t.Log(e)
}
}
// 创建app
app := mediator.NewMockPhoneApp(mediator.DefaultCloudMediator)
// 设备控制测试
fnCallAndLog(func() error {
return app.LightOpen(1)
})
fnCallAndLog(func() error {
return app.LightSwitchMode(1, 1)
})
fnCallAndLog(func() error {
return app.LightSwitchMode(1, 2)
})
fnCallAndLog(func() error {
return app.LightClose(1)
})
}
测试输出
t$ go test -v mediator_pattern_test.go
=== RUN Test_MediatorPattern
tMockSmartLight.open, id=1
tMockSmartLight.switchMode, id=1, mode=1
tMockSmartLight.switchMode, id=1, mode=2
tMockSmartLight.close, id=1
--- PASS: Test_MediatorPattern (0.00s)
PASS
ok command-line-arguments 0.002s
MockPhoneApp.go
虚拟的手机app, 用于跟云中心通信, 控制智能设备
package mediator
import (
"errors"
"fmt"
)
type MockPhoneApp struct {
mediator ICloudMediator
}
func NewMockPhoneApp(mediator ICloudMediator) *MockPhoneApp {
return &MockPhoneApp{
mediator,
}
}
func (me *MockPhoneApp) LightOpen(id int) error {
return me.lightCommand(id, "light open")
}
func (me *MockPhoneApp) LightClose(id int) error {
return me.lightCommand(id, "light close")
}
func (me *MockPhoneApp) LightSwitchMode(id int, mode int) error {
return me.lightCommand(id, fmt.Sprintf("light switch_mode %v", mode))
}
func (me *MockPhoneApp) lightCommand(id int, cmd string) error {
res := me.mediator.Command(id, cmd)
if res != "OK" {
return errors.New(res)
}
return nil
}
ICloudMediator.go
云中心面向手机app的接口
package mediator
type ICloudMediator interface {
Command(id int, cmd string) string
}
ICloudCenter.go
云中心面向智能设备的注册接口
package mediator
type ICloudCenter interface {
Register(dev ISmartDevice)
}
ISmartDevice.go
智能设备接口
package mediator
type ISmartDevice interface {
ID() int
Command(cmd string) string
}
tMockCloudMediator.go
虚拟的云中心服务类, 面向手机app实现ICloudMediator接口, 面向智能设备实现ICloudCenter接口
package mediator
import "sync"
type tMockCloudMediator struct {
mDevices map[int]ISmartDevice
mRWMutex *sync.RWMutex
}
func newMockCloudMediator() ICloudMediator {
return &tMockCloudMediator{
make(map[int]ISmartDevice),
new(sync.RWMutex),
}
}
func (me *tMockCloudMediator) Register(it ISmartDevice) {
me.mRWMutex.Lock()
defer me.mRWMutex.Unlock()
me.mDevices[it.ID()] = it
}
func (me *tMockCloudMediator) Command(id int, cmd string) string {
me.mRWMutex.RLock()
defer me.mRWMutex.RUnlock()
it,ok := me.mDevices[id]
if !ok {
return "device not found"
}
return it.Command(cmd)
}
var DefaultCloudMediator = newMockCloudMediator()
var DefaultCloudCenter = DefaultCloudMediator.(ICloudCenter)
tMockSmartLight.go
虚拟的智能灯设备, 实现ISmartDevice接口
package mediator
import (
"fmt"
"strconv"
"strings"
)
type tMockSmartLight struct {
id int
}
func NewMockSmartLight(id int) ISmartDevice {
return &tMockSmartLight{
id,
}
}
func (me *tMockSmartLight) ID() int {
return me.id
}
func (me *tMockSmartLight) Command(cmd string) string {
if cmd == "light open" {
e := me.open()
if e != nil {
return e.Error()
}
} else if cmd == "light close" {
e := me.close()
if e != nil {
return e.Error()
}
} else if strings.HasPrefix(cmd, "light switch_mode") {
args := strings.Split(cmd, " ")
if len(args) != 3 {
return "invalid switch command"
}
n, e := strconv.Atoi(args[2])
if e != nil {
return "invalid mode number"
}
e = me.switchMode(n)
if e != nil {
return e.Error()
}
} else {
return "unrecognized command"
}
return "OK"
}
func (me *tMockSmartLight) open() error {
fmt.Printf("tMockSmartLight.open, id=%v\n", me.id)
return nil
}
func (me *tMockSmartLight) close() error {
fmt.Printf("tMockSmartLight.close, id=%v\n", me.id)
return nil
}
func (me *tMockSmartLight) switchMode(mode int) error {
fmt.Printf("tMockSmartLight.switchMode, id=%v, mode=%v\n", me.id, mode)
return nil
}
中介者模式小结
中介者模式的优点
(1)减少类间依赖,将多对多依赖转化成一对多,降低了类间耦合。
(2)类间各司其职,符合迪米特法则。
中介者模式的缺点
中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。
当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。
(摘自 谭勇德 <<设计模式就该这样学>>)
(end)