您好,欢迎访问代理记账网站
  • 价格透明
  • 信息保密
  • 进度掌控
  • 售后无忧

Go设计模式(12)-桥接模式

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

本文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是通讯录软件,也有两个子类,分别是小米手机的通讯录和华为手机的通讯录。
在这里插入图片描述

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

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

  1. 分类更加合理。第一种手机是手机,软件是软件,分的很清晰,但是第二种手机和软件缺杂糅在一起,显得很乱。
  2. 组合优于继承。第一种使用组合,使得手机和软件之间的关系很弱,使得两者可以独立变化。第二种方式使用继承,软件继承自手机,不合适,而且会使继承链路变长。
  3. 修改影响小。无论是修改哪一类或者增加哪一类,第一种方案对系统的改动都要小。

还有点需要指出,两个分类使用的是聚合,使得抽象类可以方便的关联多个实现类。

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/

往期文章回顾:

招聘

  1. 字节跳动|内推大放送
  2. 字节跳动|今日头条广州服务端研发工程师内推
  3. 字节跳动|抖音电商急招上海前端开发工程
  4. 字节跳动|抖音电商上海资深服务端开发工程师-交易
  5. 字节跳动|抖音电商武汉服务端(高级)开发工程师
  6. 字节跳动|飞书大客户产品经理内推咯
  7. 字节跳动|抖音电商服务端技术岗位虚位以待
  8. 字节跳动招聘专题

设计模式

  1. Go设计模式(11)-代理模式
  2. Go设计模式(10)-原型模式
  3. Go设计模式(9)-建造者模式
  4. Go设计模式(8)-抽象工厂
  5. Go设计模式(7)-工厂模式
  6. Go设计模式(6)-单例模式
  7. Go设计模式(5)-类图符号表示法
  8. Go设计模式(4)-代码编写优化
  9. Go设计模式(4)-代码编写
  10. Go设计模式(3)-设计原则
  11. Go设计模式(2)-面向对象分析与设计
  12. Go设计模式(1)-语法

语言

  1. 一文搞懂pprof
  2. Go工具之generate
  3. Go单例实现方案
  4. Go通道实现原理
  5. Go定时器实现原理
  6. Beego框架使用
  7. Golang源码BUG追查
  8. Gin框架简洁版
  9. Gin源码剖析

架构

  1. 支付接入常规问题
  2. 限流实现2
  3. 秒杀系统
  4. 分布式系统与一致性协议
  5. 微服务之服务框架和注册中心
  6. 浅谈微服务
  7. 限流实现1
  8. CDN请求过程详解
  9. 常用缓存技巧
  10. 如何高效对接第三方支付
  11. 算法总结

存储

  1. MySQL开发规范
  2. Redis实现分布式锁
  3. 事务原子性、一致性、持久性的实现原理
  4. InnoDB锁与事务简析

网络

  1. HTTP2.0基础教程
  2. HTTPS配置实战
  3. HTTPS连接过程
  4. TCP性能优化

工具

  1. GoLand实用技巧
  2. 根据mysql表自动生成go struct
  3. Markdown编辑器推荐-typora

读书笔记

  1. 《毛选》推荐
  2. 原则
  3. 资治通鉴
  4. 敏捷革命
  5. 如何锻炼自己的记忆力
  6. 简单的逻辑学-读后感
  7. 热风-读后感
  8. 论语-读后感
  9. 孙子兵法-读后感

思考

  1. 为动员一切力量争取胜利而斗争
  2. 反对自由主义
  3. 实践论
  4. 评价自己的标准
  5. 服务端团队假期值班方案
  6. 项目流程管理
  7. 对项目管理的一些看法
  8. 对产品经理的一些思考
  9. 关于程序员职业发展的思考
  10. 关于代码review的思考

分享:

低价透明

统一报价,无隐形消费

金牌服务

一对一专属顾问7*24小时金牌服务

信息保密

个人信息安全有保障

售后无忧

服务出问题客服经理全程跟进