前言
以小說的筆法寫的設計模式系列文章,你絕對看得懂![首發於公眾號:"聊聊程式碼"]
設計模式系列·王小二需求歷險記(一)
設計模式系列·王小二需求歷險記(二)
設計模式系列·封裝、繼承、多型
設計模式系列·初探設計模式之王小二的疑問
設計模式系列·Facade模式之MVC的煩惱
設計模式系列·Adapter 模式之如何優雅的使用別人的輪子
設計模式系列·類爆炸之Bridge模式
設計模式系列·工廠方法模式之 Code Review
設計模式系列·抽象工廠模式
------華麗的分割線------
code review 的開始
小二所在的公司最近出了很多線上bug,痛定思痛,於是老大們紛紛決定落實code review機制...
很走運,C哥負責review小二訊息中心的程式碼
好一段switch...case...
"小二,我們開始吧,讓我看看前幾天你寫的程式碼"。C哥微笑道。
"好的,C哥!"
小二熟練的開啟電腦,找到訊息中心的程式碼。
"C哥,這是你之前告訴我用的橋接模式寫的!"
"嗯,寫的不錯,這樣抽象與實現就能各自獨立的變化了。"
"等等,小二,讓我看看你這段程式碼是怎麼回事?"
"稍等,C哥,我放大一些。"
小二將滑鼠聚焦在這段程式碼上...
switch ($mes_type){
case 'Sms':
$obj = new SmsMessage();
break;
case 'Email':
$obj = new EmailMessage();
break;
default:
throw new Exception('NO Message Type Found');
}
$obj->send();複製程式碼
"小二,你講講這段程式碼的邏輯。"
"好的,C哥,這段程式碼是呼叫端的程式碼,根據不同的訊息型別($mes_type
),例項化不同的訊息類。比如訊息型別傳入Sms
的時候,這時候就會例項化SmsMessage
類。"
"哦,我知道了。好一段 switch...case...,但這段程式碼有問題!"
"嗯?什麼問題啊?"小二好奇的問道
"比如我現在新增一種訊息類 AppPushMessage
,你想想,你呼叫端的程式碼是不是要改啊?"
"的確是,switch...case...這塊要變動。"
"對嘛,我只是新增了訊息型別,不應該對呼叫端的程式碼產生影響!"
"是,C哥。有沒有什麼辦法可以解決這個問題呢?"
C哥微微笑道:"辦法是有的,還不止一種呢!"
小二雙手抱拳:"請C哥不吝賜教!"
問題的本質
"小二,你覺得剛才問題的本質是什麼?"
"嗯...想不出來..."
"好吧,不難為你了。問題的本質是:呼叫端負責了太多的事情!"
"哦哦,違背了'單一職責'原則?"
"是啊,呼叫端只是負責實質的呼叫,傳送出實質的訊息而已。這才是他的職責。"
"對!我明白了,我程式碼裡呼叫端既負責呼叫相關的訊息類完成傳送訊息,又負責根據不同的引數例項化不同的訊息類。他的責任到底是負責呼叫傳送呢?還是負責例項化不同的訊息類呢?責任不明確,所以會產生耦合性的問題!"
"嗯嗯,小二悟性長進不少啊!"
"哈哈,多蒙C哥指教!"
簡單工廠模式
"C哥,我們知道了問題的本質。怎麼解決呢?"
"好,下面我們就用簡單工廠模式來解決。"
"簡單工廠模式?我好像聽說過。"
"簡單工廠,用的人挺多的,但不屬於23種GOF設計模式之一。"
"哦哦,這樣啊。"
"你看,上面的程式碼利用簡單工廠可以改寫一下。"
/****************File:MessageFactory.php*******************/
<?php
class MessageFactory{
public static function get_instance($mes_type){
switch ($mes_type){
case 'Sms':
$obj = new SmsMessage();
break;
case 'Email':
$obj = new EmailMessage();
break;
default:
throw new Exception('NO Message Type Found');
}
return $obj;
}
}複製程式碼
/********************File:Client.php**********************/
class Client{
public function main(){
$obj=MessageFactory::get_instance($mes_type);
$obj->send();
}
}複製程式碼
"你看看,呼叫端只負責呼叫訊息類進行傳送。而具體例項化哪個訊息類,這就不是呼叫端關心的了。"
"對,是啊!呼叫端只需要向簡單工廠傳送請求,簡單工廠就返回相應例項化好的物件。"
"這樣,呼叫端負責實際的訊息傳送,簡單工廠負責製造(例項化)相應的訊息物件。他們的職責分明!"
"對,像您剛才說的,我再增加一種訊息AppPush
,我也不用改呼叫端的程式碼了!"
工廠方法模式
"小二,還記不記得我剛才說的,解決上面問題的辦法不止一種。"
"嗯嗯,記得記得!還有什麼好辦法嗎?"
"有的。上面的簡單工廠,你覺得有什麼缺點嗎?"
"嗯...找不出來。要實在找缺點的話,還真有一個。比如我剛才新增了AppPush
訊息型別,就要修改上面的MessageFactory
工廠類。"
"是,這樣就違背了 開放-封閉 原則。我們應該對擴充套件開發,而對修改關閉。因為修改,可能會帶來意想不到的bug。"
"對,確實是。但簡單工廠確實解決了單一職責的問題,也不失為一種好的模式。那怎麼才能既解決單一職責的問題,又不違背開閉原則呢?"
"有一種設計模式:工廠方法模式。可以解決你說的問題。"
"太好了!C哥你能簡單介紹一下嗎?"
"首先看一下工廠方法模式的類圖吧!"
"好的,C哥!"
"看這個類圖,你能明白工廠方法模式大致的意圖嗎?"
"我看看。C哥,工廠方法模式,是不是將物件的建立,延遲到了子類中去執行?也就是每個子類工廠去負責建立相關的物件?"
"對,這樣的話,我就不用在工廠類中寫一大堆 switch...case... 了。當出現一種新訊息類的時候,我只需要擴充套件出一個相應的工廠類來就行了。"
"嗯嗯,明白了,這就符合開閉原則了!"
"但是,C哥,我還有一個疑問。雖然工廠方法模式符合了開閉原則,但是,我要在呼叫端決定使用哪個工廠啊?"
"對,這的確是個問題。但是我們有很多種解決的辦法:你可以寫一個配置檔案,每次去讀這個配置檔案來決定使用哪個工廠。"
"寫一個配置檔案,可以是可以,但總覺得不優雅。"
"哈哈,有沒有聽說過反射?反射也可以解決這個問題。"
"哦哦,這樣啊!厲害!"
"小二,用工廠方法模式,你畫一下上面程式碼的UML類圖。"
不一會,小二就畫出了工廠方法模式的類圖。
"不錯嘛,小二,我這裡先用反射,給你看看程式碼的實現。具體反射的機制、原理,你自己去查一下吧!"
"好的,C哥!"
/*************抽象類:MessageFactory.php*******************/
<?php
abstract class MessageFactory{
abstract public function get_instance();
}
/*************EmailFactory.php*******************/
<?php
class EmailFactory extends MessageFactory {
public function get_instance()
{
return new EmailMessage();
}
}
/*************SmsFactory.php*******************/
<?php
class SmsFactory extends MessageFactory {
public function get_instance()
{
return new SmsMessage();
}
}
/*************呼叫端:Client.php***************/
<?php
class Client{
public function main($mes_type){
//利用反射,消除呼叫端的邏輯判斷
$reflection=new ReflectionClass($mes_type.'Factory');
$factory=$reflection->newInstance();
$mes_obj=$factory->get_instance();
$mes_obj->send();
}
}複製程式碼
"哇塞!C哥太棒了。這解決辦法非常好!"
"不能說非常好,但解決了問題。"
review結束
不知不覺中,2個小時過去了,code review也接近尾聲了。
小二望向窗外,看著天邊的雲彩慢悠悠的飄著,想想自己剛學到的設計模式,嘴角不自覺的露出了微笑,coder的快樂,或許就是這麼簡單...
轉載宣告:本文轉載自「聊聊程式碼」,搜尋「talkpoem」即可關注。
關注「聊聊程式碼」,讓我們一起聊聊“左手程式碼右手詩”的事兒。