設計模式系列·類爆炸之 Bridge 模式

迷之微笑

通過 C 哥的精心指導,消息中心終於上線!代碼運行了半個月,穩健無 bug 。
王小二託着下腮,看着代碼,一抹迷之微笑隨之閃現^_^。做爲一名有追求的碼農,此時的快樂或許只有本身能懂。php

消息中心的重構

一天清晨,小二凝神聚力,手指在鍵盤間有節奏的敲擊着,一行行代碼躍然屏上。不知不覺,老大在小二背後站了半天了...java

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

"好的,我們如今正在搞服務拆分。而消息中心又是一個通用的服務,因此我想把消息中心拆出來,做爲底層服務。"
"好啊,早應該這樣了!"sql

"嗯,具體發送消息的邏輯,這塊交給 java 組同窗去寫。你只須要按照約定的數據格式,將數據 push 到隊列裏去, java 那邊去消費就能夠了。"
"嗯...能夠,隊列用什麼實現呢?"編程

"關於隊列,此次須要你支持兩種方式:一種是 redis 、一種是 mq"
"也就是說我既支持往 redis 隊列裏面 push 數據,也支持往 mq 裏 push 數據?"設計模式

"是的,就是這樣,這塊你好好設計下吧!"
"好的,放心吧老大!"微信

設計類圖

小二這兩天正在研究設計模式,既然接到了重構的新需求,那就好好大展一番身手吧!併發

不一會,小二就理出了大致的思路:函數

發送消息,分爲 3 步:
1 、不一樣的消息(短信、微信)組裝各自的數據格式和內容;
2 、消息可使用不一樣的方式(redis 、 mq)推送到隊列裏;
3 、使用一個 send()方法,先從步驟 1 獲取數據,再利用步驟 2 的方法 push 到相應的隊列裏。學習

思路清楚了,小二立刻畫出了類圖:

classboom00.png-35.7kB

小二反覆看了幾遍本身設計的類圖:
嗯,基本實現了需求。
1 、消息分爲短信消息和微信消息(SmsMessage 和 WechatMessage)
2 、相同的消息既能夠經過 redis 發送,又能夠經過 Mq 發送。
沒毛病, great!

類爆炸

和往常同樣,比較大的設計,仍是得請 C 哥把把關。
小二找到 C 哥,詳細介紹了本身的需求和設計。

"嗯...小二啊,問題是解決了,但設計看起來有點問題啊!"
"啊?有問題?請 C 哥指教"

"這個會引發類爆炸!"
"啥?類還會爆炸?你別逗我了"

"哈哈,不信?來,我讓你看看類怎麼爆炸的。假設需求要你新增 Email 消息類型,你再設計下類圖"
"好的, C 哥你等下,立刻設計出來"

不一會,王小二就設計出了新的類圖:

classboom1.png-46.9kB

"小二,紅色部分是你新增的 3 個類。"
"嗯嗯,是的!"

"好,在此基礎上,你再增長 Mysql 隊列的發送方式"
"好的!"

小二拿着新的類圖找到了 C 哥:

classboom2.png-54.7kB

「小二,剛纔只是讓你新增一種消息類型和發送方式,你看看一共增長了幾個類?」
「 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 個。一個是消息類型會變化,一個是發送方式會變化。」
想好後,小二立刻畫了出來。
22.png-9.7kB
"嗯,不錯,小二你解釋下吧"

小二解釋道:"
變化的點有 2 個:一個是消息類型[Sms 、 Wechat...],一個是發送方式[redis 、 mq...]。

因此我把他們各自都封裝了起來,成爲 2 個獨立的抽象類:Message 和 SendType 。

Message 類負責組裝好本身消息類型的數據(combine_data()),併發送(send())出去。
SendType 類負責將數據 push(push_to_queue())進相應的隊列。"

"不錯嘛,小二,我在你類圖的基礎上擴展下,你就知道怎麼解決類爆炸的問題了。"
"哇塞,好的, C 哥!"

不一會, C 哥就在小二的基礎上,畫出了完整的類圖:
ok1.png-27kB
"看不太懂, 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 哥,這是運行結果"

image_1bdjqul59f110vkqqrrel138f2h.png-11.4kB

"哈哈,確實沒問題,不錯嘛小二!"
"C 哥指點的好,謝謝 C 哥,又學習了一種強大的設計模式!"

結語

設計模式如此強大,從 bridge 就可見其不通常。
那到底什麼是設計模式呢?有沒有一個通俗的定義呢?

其實,通俗點說:

設計模式,是針對特定問題的,反覆出現的解決方案,這種方案被抽象化、模板化。而且隨着時間的流逝,被歷史證實這是優秀的解決方案。

因此,跟着王小二一塊兒好好的學習設計模式吧,相信你終將邁入"左手代碼右手詩"的天地!^_^

轉載聲明:本文轉載自「聊聊代碼」,搜索「talkpoem」便可關注。

關注「聊聊代碼」,讓咱們一塊兒聊聊「左手代碼右手詩」的事兒。
qrcode.jpg-39.1kB

相關文章
相關標籤/搜索