類爆炸之 Bridge 模式
迷之微笑
經過C哥的精心指導,消息中心終于上線!代碼運行了半個月,穩定無bug。
王小二托著下腮,看著代碼,一抹迷之微笑隨之閃現^_^。作為一名有追求的碼農,此時的快樂或許只有自己能懂。
消息中心的重構
一天清晨,小二凝神聚力,手指在鍵盤間有節奏的敲擊著,一行行代碼躍然屏上。不知不覺,老大在小二背后站了半天了...
"小二,之前消息中心是你做的吧?"
"嗯嗯,是的。"
"好的,咱們現在正在搞服務拆分。而消息中心又是一個通用的服務,所以我想把消息中心拆出來,作為底層服務。"
"好啊,早應該這樣了!"
"嗯,具體發送消息的邏輯,這塊交給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();</pre>
"嗯,看起來沒毛病,我看看你的運行結果。"
"好的,C哥,這是運行結果"
image_1bdjqul59f110vkqqrrel138f2h.png-11.4kB
"哈哈,確實沒問題,不錯嘛小二!"
"C哥指點的好,謝謝C哥,又學習了一種強大的設計模式!"
結語
設計模式如此強大,從bridge就可見其不一般。
那到底什么是設計模式呢?有沒有一個通俗的定義呢?
其實,通俗點說:
設計模式,是針對特定問題的,反復出現的解決方案,這種方案被抽象化、模板化。并且隨著時間的流逝,被歷史證明這是優秀的解決方案。
所以,跟著王小二一起好好的學習設計模式吧,相信你終將邁入"左手代碼右手詩"的天地!^_^
來自:https://juejin.im/post/58f00cc28d6d8100646d0ffc