設計模式系列·工廠方法模式之Code Review

子路發表於2017-04-24

前言

以小說的筆法寫的設計模式系列文章,你絕對看得懂![首發於公眾號:"聊聊程式碼"]

設計模式系列·王小二需求歷險記(一)
設計模式系列·王小二需求歷險記(二)
設計模式系列·封裝、繼承、多型
設計模式系列·初探設計模式之王小二的疑問
設計模式系列·Facade模式之MVC的煩惱
設計模式系列·Adapter 模式之如何優雅的使用別人的輪子
設計模式系列·類爆炸之Bridge模式
設計模式系列·工廠方法模式之 Code Review
設計模式系列·抽象工廠模式

------華麗的分割線------

code review 的開始

小二所在的公司最近出了很多線上bug,痛定思痛,於是老大們紛紛決定落實code review機制...
很走運,C哥負責review小二訊息中心的程式碼

設計模式系列·工廠方法模式之Code Review
code 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哥!"

設計模式系列·工廠方法模式之Code Review

"看這個類圖,你能明白工廠方法模式大致的意圖嗎?"
"我看看。C哥,工廠方法模式,是不是將物件的建立,延遲到了子類中去執行?也就是每個子類工廠去負責建立相關的物件?"

"對,這樣的話,我就不用在工廠類中寫一大堆 switch...case... 了。當出現一種新訊息類的時候,我只需要擴充套件出一個相應的工廠類來就行了。"
"嗯嗯,明白了,這就符合開閉原則了!"

"但是,C哥,我還有一個疑問。雖然工廠方法模式符合了開閉原則,但是,我要在呼叫端決定使用哪個工廠啊?"
"對,這的確是個問題。但是我們有很多種解決的辦法:你可以寫一個配置檔案,每次去讀這個配置檔案來決定使用哪個工廠。"

"寫一個配置檔案,可以是可以,但總覺得不優雅。"
"哈哈,有沒有聽說過反射反射也可以解決這個問題。"

"哦哦,這樣啊!厲害!"
"小二,用工廠方法模式,你畫一下上面程式碼的UML類圖。"

不一會,小二就畫出了工廠方法模式的類圖。

設計模式系列·工廠方法模式之Code Review

"不錯嘛,小二,我這裡先用反射,給你看看程式碼的實現。具體反射的機制、原理,你自己去查一下吧!"
"好的,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」即可關注。

關注「聊聊程式碼」,讓我們一起聊聊“左手程式碼右手詩”的事兒。

設計模式系列·工廠方法模式之Code Review

相關文章