类爆炸之Bridge模式

前言

以小说的笔法写的设计模式系列文章,你绝对看得懂![首发于公众号:"聊聊代码"]javascript

设计模式系列·王小二需求历险记(一)
设计模式系列·王小二需求历险记(二)
设计模式系列·封装、继承、多态
设计模式系列·初探设计模式之王小二的疑问
设计模式系列·Facade模式之MVC的烦恼
设计模式系列·Adapter 模式之如何优雅的使用别人的轮子
设计模式系列·类爆炸之Bridge模式
设计模式系列·工厂方法模式之 Code Review
设计模式系列·抽象工厂模式php

------华丽的分割线------java

迷之微笑

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

消息中心的重构

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

"小二,以前消息中心是你作的吧?"
"嗯嗯,是的。"编程

"好的,我们如今正在搞服务拆分。而消息中心又是一个通用的服务,因此我想把消息中心拆出来,做为底层服务。"
"好啊,早应该这样了!"设计模式

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

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

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

设计类图

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

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

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

思路清楚了,小二立刻画出了类图:

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

类爆炸

和往常同样,比较大的设计,仍是得请C哥把把关。
小二找到C哥,详细介绍了本身的需求和设计。

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

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

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

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

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

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

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

“小二,刚才只是让你新增一种消息类型和发送方式,你看看一共增长了几个类?”
“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个。一个是消息类型会变化,一个是发送方式会变化。”
想好后,小二立刻画了出来。


"嗯,不错,小二你解释下吧"

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

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

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

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

不一会,C哥就在小二的基础上,画出了完整的类图:


"看不太懂,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哥,这是运行结果"

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

结语

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

其实,通俗点说:

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

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

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

关注「聊聊代码」,让咱们一块儿聊聊“左手代码右手诗”的事儿。


设计模式系列·王小二需求历险记(一)
设计模式系列·王小二需求历险记(二)
设计模式系列·封装、继承、多态
设计模式系列·初探设计模式之王小二的疑问
设计模式系列·Facade模式之MVC的烦恼
设计模式系列·类爆炸之Bridge模式