设计模式系列·类爆炸之 Bridge 模式

代码逐风者
• 阅读 6692

迷之微笑

经过 C 哥的精心指导,消息中心终于上线!代码运行了半个月,稳健无 bug 。
王小二托着下腮,看着代码,一抹迷之微笑随之闪现^_^。作为一名有追求的码农,此时的快乐或许只有自己能懂。

消息中心的重构

一天清晨,小二凝神聚力,手指在键盘间有节奏的敲击着,一行行代码跃然屏上。不知不觉,老大在小二背后站了半天了...

"小二,之前消息中心是你做的吧?"
"嗯嗯,是的。"

"好的,咱们现在正在搞服务拆分。而消息中心又是一个通用的服务,所以我想把消息中心拆出来,作为底层服务。"
"好啊,早应该这样了!"

"嗯,具体发送消息的逻辑,这块交给 java 组同学去写。你只需要按照约定的数据格式,将数据 push 到队列里去, java 那边去消费就可以了。"
"嗯...可以,队列用什么实现呢?"

"关于队列,这次需要你支持两种方式:一种是 redis 、一种是 mq"
"也就是说我既支持往 redis 队列里面 push 数据,也支持往 mq 里 push 数据?"

"是的,就是这样,这块你好好设计下吧!"
"好的,放心吧老大!"

设计类图

小二这两天正在研究设计模式,既然接到了重构的新需求,那就好好大展一番身手吧!

不一会,小二就理出了大体的思路:

发送消息,分为 3 步:
1 、不同的消息(短信、微信)组装各自的数据格式和内容;
2 、消息可以使用不同的方式(redis 、 mq)推送到队列里;
3 、使用一个 send()方法,先从步骤 1 获取数据,再利用步骤 2 的方法 push 到相应的队列里。

思路清楚了,小二马上画出了类图:

设计模式系列·类爆炸之 Bridge 模式

小二反复看了几遍自己设计的类图:
嗯,基本实现了需求。
1 、消息分为短信消息和微信消息(SmsMessage 和 WechatMessage)
2 、相同的消息既可以通过 redis 发送,又可以通过 Mq 发送。
没毛病, great!

类爆炸

和往常一样,比较大的设计,还是得请 C 哥把把关。
小二找到 C 哥,详细介绍了自己的需求和设计。

"嗯...小二啊,问题是解决了,但设计看起来有点问题啊!"
"啊?有问题?请 C 哥指教"

"这个会引起类爆炸!"
"啥?类还会爆炸?你别逗我了"

"哈哈,不信?来,我让你看看类怎么爆炸的。假设需求要你新增 Email 消息类型,你再设计下类图"
"好的, C 哥你等下,马上设计出来"

不一会,王小二就设计出了新的类图:

设计模式系列·类爆炸之 Bridge 模式

"小二,红色部分是你新增的 3 个类。"
"嗯嗯,是的!"

"好,在此基础上,你再增加 Mysql 队列的发送方式"
"好的!"

小二拿着新的类图找到了 C 哥:

设计模式系列·类爆炸之 Bridge 模式

“小二,刚才只是让你新增一种消息类型和发送方式,你看看一共增加了几个类?”
“ 1.2.3..6 ,一共新增了 6 个类!”

"好,你现在一共有 13 个类,假设再让你新增一种消息类型和发送方式,你又会新增多少个类?"
"嗯...会新增 8 个类,到时候就 13+8=21 个类了..."

“类太多了,爆炸了吧?哈哈,这就是类爆炸”
“确实是,类确实太多了!但是,怎么解决呢?...”

Bridge 模式登场

"小二啊,你还记不记得前面我给你讲的四人团的三条建议?"
“嗯,记得:

 
 1 、针对接口编程;
 2 、优先使用对象组合,而不是类继承;
 3 、找到并封装变化的点。

“对,就是这 3 点。你看看,你的设计就违背了上面的原则。” C 哥说道。
"嗯?还真违反了???"王小二看了一会...

"哦...是的, C 哥,确实是。违反了第 2 点,你看我类图中使用的都是继承,这个继承间耦合性太高了,太庞大了!"
“是的,现在我们就用 Bridge 模式把他拆出来。”

"我先给你讲讲 Bridge 模式的基本定义吧!"
“好的, C 哥!”

Bridge 模式,也即桥梁模式,四人团的说法是:“将抽象部分与它的实现部分分离,使它们都可以独立地变化。”

“啊? C 哥,表示完全听不懂...”
"哈哈,正常,你一下能听懂才怪呢,这句话很容易使初学者产生误解,我们边实践,边解释这个定义。"

“小二,你刚才不是说四人团建议:‘找到并封装变化的点’吗?你现在在你的设计中找到这些变化的点,并封装起来。”
“好的,C 哥,我想想...”

小二想了一会:“变化的点有 2 个。一个是消息类型会变化,一个是发送方式会变化。”
想好后,小二马上画了出来。
设计模式系列·类爆炸之 Bridge 模式
"嗯,不错,小二你解释下吧"

小二解释道:"
变化的点有 2 个:一个是消息类型[Sms 、 Wechat...],一个是发送方式[redis 、 mq...]。

所以我把他们各自都封装了起来,成为 2 个独立的抽象类:Message 和 SendType 。

Message 类负责组装好自己消息类型的数据(combine_data()),并发送(send())出去。
SendType 类负责将数据 push(push_to_queue())进相应的队列。"

"不错嘛,小二,我在你类图的基础上扩展下,你就知道怎么解决类爆炸的问题了。"
"哇塞,好的, C 哥!"

不一会, C 哥就在小二的基础上,画出了完整的类图:
设计模式系列·类爆炸之 Bridge 模式
"看不太懂, C 哥你解释下吧!"

C 哥解释道:"
小二你看,消息有 2 种类型:短信和微信。

但不论是短信和微信,他们都应该知道自己的消息格式和内容。
并且,他们得把自己发送出去,也就是 push 到相应的队列里面去。

而如何 push 到队列里面去呢?这又有 2 种实现方式,一种是 redis 队列,一种是 mq 队列。
也就是,实现发送这个动作,得知道如何发送。

你看这里,我没有用你最初设计的类继承的方法:
这里的抽象部分:即是 Message 的抽象;
这里的实现部分:即是 SendType 的实现。

在抽象部分与实现部分之间搭个桥,使抽象部分可以引用实现部分的对象,就是桥接模式。

这样使用对象组合的方式,特别的灵活。"

"哇塞, C 哥,这个桥接威力好大啊!"
"是啊,桥接模式比较难,但也更有用。你看,这样不管你是增加一种新的消息类型还是一种新的发送方式,他们之间没有耦合,可以独立的变化。"

"是啊,这样类爆炸的问题也就没有了,冗余减少了,代码更好维护!"
"是这样的!"

代码实现

见证了 bridge 模式的威力之后,小二迫不及待的写出了相应的伪代码:

"C 哥,你帮我看下我写的代码思路对吗?"
"好的,我看看..."

<?php
//消息抽象类
abstract class Message{
    //定义发送方式对象与消息数据
    public $send_type_obj;
    public $data;

    //构造函数
    public function __construct($send_type_obj,$data)
    {
        $this->send_type_obj=$send_type_obj;
        $this->data=$data;
    }

    //抽象类:不同的消息来重写此方法,以得到不同的消息数据
    abstract public function combine_data();

    //桥接到外部对象(引用外部对象, push 到相应的队列)
    public function push_to_queue($data){
        if($this->send_type_obj instanceof SendType){
            $this->send_type_obj->push_to_queue($data);
        }
    }

    //完成发送
    public function send(){
        $combined_data=$this->combine_data();
        $this->push_to_queue($combined_data);
    }
}

//短信消息类
class SmsMessage extends Message {
    //发送短信消息数据
    public function combine_data(){
        return 'sms combined data:'.$this->data;
    }
}

//微信消息类
class WechatMessage extends Message {
    //发送微信消息数据
    public function combine_data(){
        return 'wechat combined data:'.$this->data;
    }
}

//发送方式抽象类
abstract class SendType{
    abstract public function push_to_queue($data);
}

//Redis 发送方式类
class RedisSendType extends SendType {
    //将消息 push 到 redis 队列里,完成发送
    public function push_to_queue($data)
    {
        echo  $data." has sent by redis queue\n";
    }
}

//Mq 发送方式类
class MqSendType extends SendType {
    //将消息 push 到 mq 队列里,完成发送
    public function push_to_queue($data)
    {
        echo  $data." has sent by mq queue\n";
    }
}

/************Test Case*************/

//实例化不同的发送方式类
$redis_send_obj=new RedisSendType();
$mq_send_obj= new MqSendType();

//通过 redis 发送短信
$sms_redis_obj=new SmsMessage($redis_send_obj,'123');
$sms_redis_obj->send();

//通过 redis 发送微信
$wechat_redis_obj=new WechatMessage($redis_send_obj,'456');
$wechat_redis_obj->send();

//通过 mq 发送短信
$sms_mq_obj=new SmsMessage($mq_send_obj,'789');
$sms_mq_obj->send();

//通过 mq 发送微信
$wechat_mq_obj=new WechatMessage($mq_send_obj,'100');
$wechat_mq_obj->send();

"嗯,看起来没毛病,我看看你的运行结果。"
"好的, C 哥,这是运行结果"

设计模式系列·类爆炸之 Bridge 模式

"哈哈,确实没问题,不错嘛小二!"
"C 哥指点的好,谢谢 C 哥,又学习了一种强大的设计模式!"

结语

设计模式如此强大,从 bridge 就可见其不一般。
那到底什么是设计模式呢?有没有一个通俗的定义呢?

其实,通俗点说:

设计模式,是针对特定问题的,反复出现的解决方案,这种方案被抽象化、模板化。并且随着时间的流逝,被历史证明这是优秀的解决方案。

所以,跟着王小二一起好好的学习设计模式吧,相信你终将迈入"左手代码右手诗"的天地!^_^

转载声明:本文转载自「聊聊代码」,搜索「talkpoem」即可关注。

关注「聊聊代码」,让我们一起聊聊“左手代码右手诗”的事儿。
设计模式系列·类爆炸之 Bridge 模式

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
徐小夕 徐小夕
4年前
15分钟带你了解前端工程师必知的javascript设计模式(附详细思维导图和源码)
前言设计模式是一个程序员进阶高级的必备技巧,也是评判一个工程师工作经验和能力的试金石.设计模式是程序员多年工作经验的凝练和总结,能更大限度的优化代码以及对已有代码的合理重构.作为一名合格的前端工程师,学习设计模式是对自己工作经验的另一种方式的总结和反思,也是开发高质量,高可维护性,可扩展性代码的重要手段.我们所熟知的金典的几大框架,比如jquery,
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
java24种设计模式
一、设计模式定义  设计模式(DesignPattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。二、设计模式分类  经典模式只有23个(还有简单工厂模式),它们各具特色,每个模式都为某一个可重复的设计问题提供了一套解决方案。  根据它们的用
Stella981 Stella981
3年前
Solon rpc 之 SocketD 协议
Solonrpc之SocketD协议系列Solonrpc之SocketD协议概述(https://my.oschina.net/noear/blog/4888445)Solonrpc之SocketD协议消息上报模式(https://my.oschina.net/noear/bl
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
PHP设计模式之模板方法模式
PHP设计模式之模板方法模式模板方法模式,也是我们经常会在不经意间有会用到的模式之一。这个模式是对继承的最好诠释。当子类中有重复的动作时,将他们提取出来,放在父类中进行统一的处理,这就是模板方法模式的最简单通俗的解释。就像我们平时做项目,每次的项目流程实都差不多,都有调研、开发、测试、部署上线等流程。而具体到每个项目中,这些
Wesley13 Wesley13
3年前
00_设计模式之语言选择
设计模式之语言选择设计模式简介背景设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式(Designpattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的