桥接模式并不常用,而且桥接模式的概念比较抽象。桥接模式一般用于有多种分类的情况,如果实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让他们独立变化,减少他们之间的耦合。
本文UML类图链接为:https://www.processon.com/view/link/609b39d6f346fb5a37705da6
本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/12bridge.go
1.定义
1.1桥接模式
桥接模式: 将抽象部分与它的实现部分分离,使他们都可以独立地变化。
UML类图:
1.2分析
单看桥接模式的定义和UML类图,比较难理解这个模式,如果放到指定场景下,就容易理解的多。这里借用一下《大话设计模式》里的例子:
Abstraction是手机类,RefinedAbstractionA指小米手机,RefinedAbstractionB指华为手机。
我是一手机软件提供商,Implementor是手机软件,ConcreteImplementorA是游戏软件,理论上ConcreteImplementorA应该有两个子类,分别是小米手机的的游戏和华为手机的游戏,ConcretelmplementorB是通讯录软件,也有两个子类,分别是小米手机的通讯录和华为手机的通讯录。
看这个设计的话,大家可能觉得平平无奇,但真正设计的时候,很多同学可能不会拆开两类,有可能按照品牌来设计,如手机品牌、手机品牌下包含对应的应用,或者按照手机软件来设计,如手机软件、软件下包含对应的手机。类似于这种
第二种设计肯定没有第一种设计好,但好在哪里呢?
- 分类更加合理。第一种手机是手机,软件是软件,分的很清晰,但是第二种手机和软件却杂糅在一起,显得很乱。
- 组合优于继承。第一种使用组合,使得手机和软件之间的关系很弱,使得两者可以独立变化。第二种方式使用继承,软件继承自手机,不合适,而且会使继承链路变长。
- 修改影响小。无论是修改哪一类或者增加哪一类,第一种方案对系统的改动都要小。
还有点需要指出,两个分类使用的是聚合,使得抽象类可以方便的关联多个实现类。
2.使用场景
在发现我们需要多角度去分类实现对象,而只用继承会造成大量的类增加,不能满足开放-封闭原则时,就应该要考虑用桥接模式了。
以前工作的时候,用户触达方式支持SMS、Email、AppPush、站内信,但这些方式没有系统化,散乱在各个代码中。后来要做用户旅程,本来想趁着这个时机将这些触达方式系统化,但后来因为各种原因没有成行。正好趁着这个计划,写个简单版的触达系统。
3.代码实现
这个触达系统的业务场景是:已经定义好触达的紧急情况,触达需要的数据来源不同,当运营使用的时候,根据触达紧急情况,配置好数据(文案、收件人等)即可。可以看出:一个分类是触达方式、一个分类是触达紧急情况。
package main
import "fmt"
/**
* @Description: 消息发送接口
*/
type MessageSend interface {
send(msg string)
}
/**
* @Description: 短信消息
*/
type SMS struct {
}
func (s *SMS) send(msg string) {
fmt.Println("sms 发送的消息内容为: " + msg)
}
/**
* @Description: 邮件消息
*/
type Email struct {
}
func (e *Email) send(msg string) {
fmt.Println("email 发送的消息内容为: " + msg)
}
/**
* @Description: AppPush消息
*/
type AppPush struct {
}
func (a *AppPush) send(msg string) {
fmt.Println("appPush 发送的消息内容为: " + msg)
}
/**
* @Description: 站内信消息
*/
type Letter struct {
}
func (l *Letter) send(msg string) {
fmt.Println("站内信 发送的消息内容为: " + msg)
}
/**
* @Description: 用户触达父类,包含触达方式数组messageSends
*/
type Touch struct {
messageSends []MessageSend
}
/**
* @Description: 触达方法,调用每一种方式进行触达
* @receiver t
* @param msg
*/
func (t *Touch) do(msg string) {
for _, s := range t.messageSends {
s.send(msg)
}
}
/**
* @Description: 紧急消息做用户触达
*/
type TouchUrgent struct {
base Touch
}
/**
* @Description: 紧急消息,先从db中获取各种信息,然后使用各种触达方式通知用户
* @receiver t
* @param msg
*/
func (t *TouchUrgent) do(msg string) {
fmt.Println("touch urgent 从db获取接收人等信息")
t.base.do(msg)
}
/**
* @Description: 普通消息做用户触达
*/
type TouchNormal struct {
base Touch
}
/**
* @Description: 普通消息,先从文件中获取各种信息,然后使用各种触达方式通知用户
* @receiver t
* @param msg
*/
func (t *TouchNormal) do(msg string) {
fmt.Println("touch normal 从文件获取接收人等信息")
t.base.do(msg)
}
func main() {
//触达方式
sms := &SMS{}
appPush := &AppPush{}
letter := &Letter{}
email := &Email{}
//根据触达类型选择触达方式
fmt.Println("-------------------touch urgent")
touchUrgent := TouchUrgent{
base: Touch{
messageSends: []MessageSend{sms, appPush, letter, email},
},
}
touchUrgent.do("urgent情况")
fmt.Println("-------------------touch normal")
touchNormal := TouchNormal{ //
base: Touch{
messageSends: []MessageSend{sms, appPush, letter, email},
},
}
touchNormal.do("normal情况")
}
输出:
➜ myproject go run main.go
-------------------touch urgent
touch urgent 从db获取接收人等信息
sms 发送的消息内容为: urgent情况
appPush 发送的消息内容为: urgent情况
站内信 发送的消息内容为: urgent情况
email 发送的消息内容为: urgent情况
-------------------touch normal
touch normal 从文件获取接收人等信息
sms 发送的消息内容为: normal情况
appPush 发送的消息内容为: normal情况
站内信 发送的消息内容为: normal情况
email 发送的消息内容为: normal情况
真正的需求实现方式很多,一种可以向我这样,根据紧急程度制定好类型,运营使用的时候选择指定类型,并配置文案、收件人等信息,紧急程度使用哪些触达方式可以配置化,这样开闭性会更好。另一种可以不设置类型,使用哪些触达方式也是运营自己挑选,如果能够保证所有操作一致的话,Touch类只需要一个即可,都无需继承。
总结
桥接模式符合了开放-封闭原则、里氏替换原则、依赖倒转原则。使用桥接模式,一定要看一下场景中是否有多种分类、且分类之间有一定关联。如果符合的话,建议用桥接模式,这样不同分类可以独立变化,相互之间不影响。
最后
大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)
我的个人博客为:https://shidawuhen.github.io/
往期文章回顾:
招聘
- 字节跳动|内推大放送
- 字节跳动|今日头条广州服务端研发工程师内推
- 字节跳动|抖音电商急招上海前端开发工程
- 字节跳动|抖音电商上海资深服务端开发工程师-交易
- 字节跳动|抖音电商武汉服务端(高级)开发工程师
- 字节跳动|飞书大客户产品经理内推咯
- 字节跳动|抖音电商服务端技术岗位虚位以待
- 字节跳动招聘专题
设计模式
- Go设计模式(11)-代理模式
- Go设计模式(10)-原型模式
- Go设计模式(9)-建造者模式
- Go设计模式(8)-抽象工厂
- Go设计模式(7)-工厂模式
- Go设计模式(6)-单例模式
- Go设计模式(5)-类图符号表示法
- Go设计模式(4)-代码编写优化
- Go设计模式(4)-代码编写
- Go设计模式(3)-设计原则
- Go设计模式(2)-面向对象分析与设计
- Go设计模式(1)-语法
语言
架构
存储
网络
工具
读书笔记
思考