golang设计模式——桥接模式

桥接模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zr20L6Hh-1660141802487)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220810213351989.png)]

桥接模式并不常用,而且桥接模式的概念比较抽象。桥接模式一般用于有多种分类的情况,如果实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让他们独立变化,减少他们之间的耦合。

**桥接模式:**将抽象部分与它的实现部分分离,使他们都可以独立地变化。

UML类图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9bKykU8b-1660141802488)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220810221115625.png)]

单看桥接模式的定义和UML类图,比较难理解这个模式,如果放到指定场景下,就容易理解的多。这里借用一下《大话设计模式》里的例子:

Abstraction是手机类,RefinedAbstractionA指小米手机,RefinedAbstractionB指华为手机。

我是一手机软件提供商,Implementor是手机软件,ConcreteImplementorA是游戏软件,理论上ConcreteImplementorA应该有两个子类,分别是小米手机的的游戏和华为手机的游戏,ConcretelmplementorB是通讯录软件,也有两个子类,分别是小米手机的通讯录和华为手机的通讯录。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eBzthrPZ-1660141802488)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220810221444440.png)]

看这个设计的话,大家可能觉得平平无奇,但真正设计的时候,很多同学可能不会拆开两类,有可能按照品牌来设计,如手机品牌、手机品牌下包含对应的应用,或者按照手机软件来设计,如手机软件、软件下包含对应的手机。类似于这种

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zoLezwgZ-1660141802489)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220810221934196.png)]

第二种设计肯定没有第一种设计好,但好在哪里呢?

  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)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值