桥接模式
目录
- 桥接模式
-
- UML类图:
- 使用场景
- 代码实现
- 总结
- 实例
-
- 代码
- 单元测试
桥接模式并不常用,而且桥接模式的概念比较抽象。桥接模式一般用于有多种分类的情况,如果实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让他们独立变化,减少他们之间的耦合。
**桥接模式:**将抽象部分与它的实现部分分离,使他们都可以独立地变化。
UML类图:
单看桥接模式的定义和UML类图,比较难理解这个模式,如果放到指定场景下,就容易理解的多。这里借用一下《大话设计模式》里的例子:
Abstraction是手机类,RefinedAbstractionA指小米手机,RefinedAbstractionB指华为手机。
我是一手机软件提供商,Implementor是手机软件,ConcreteImplementorA是游戏软件,理论上ConcreteImplementorA应该有两个子类,分别是小米手机的的游戏和华为手机的游戏,ConcretelmplementorB是通讯录软件,也有两个子类,分别是小米手机的通讯录和华为手机的通讯录。
看这个设计的话,大家可能觉得平平无奇,但真正设计的时候,很多同学可能不会拆开两类,有可能按照品牌来设计,如手机品牌、手机品牌下包含对应的应用,或者按照手机软件来设计,如手机软件、软件下包含对应的手机。类似于这种
第二种设计肯定没有第一种设计好,但好在哪里呢?
1、 分类更加合理第一种手机是手机,软件是软件,分的很清晰,但是第二种手机和软件却杂糅在一起,显得很乱;
2、 组合优于继承第一种使用组合,使得手机和软件之间的关系很弱,使得两者可以独立变化第二种方式使用继承,软件继承自手机,不合适,而且会使继承链路变长;
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类只需要一个即可,都无需继承。
总结
桥接模式符合了开放-封闭原则、里氏替换原则、依赖倒转原则。使用桥接模式,一定要看一下场景中是否有多种分类、且分类之间有一定关联。如果符合的话,建议用桥接模式,这样不同分类可以独立变化,相互之间不影响。
实例
代码
package bridge
// IMsgSender IMsgSender
type IMsgSender interface {
Send(msg string) error
}
// EmailMsgSender 发送邮件
// 可能还有 电话、短信等各种实现
type EmailMsgSender struct {
emails []string
}
// NewEmailMsgSender NewEmailMsgSender
func NewEmailMsgSender(emails []string) *EmailMsgSender {
return &EmailMsgSender{
emails: emails}
}
// Send Send
func (s *EmailMsgSender) Send(msg string) error {
// 这里去发送消息
return nil
}
// INotification 通知接口
type INotification interface {
Notify(msg string) error
}
// ErrorNotification 错误通知
// 后面可能还有 warning 各种级别
type ErrorNotification struct {
sender IMsgSender
}
// NewErrorNotification NewErrorNotification
func NewErrorNotification(sender IMsgSender) *ErrorNotification {
return &ErrorNotification{
sender: sender}
}
// Notify 发送通知
func (n *ErrorNotification) Notify(msg string) error {
return n.sender.Send(msg)
}
单元测试
func TestErrorNotification_Notify(t *testing.T) {
sender := NewEmailMsgSender([]string{
"test@test.com"})
n := NewErrorNotification(sender)
err := n.Notify("test msg")
assert.Nil(t, err)
}
版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: