08、桥接模式.md

桥接模式

目录

  • 桥接模式
    • 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)
}

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: