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

code review 的開始

小二所在的公司最近出了不少線上bug,痛定思痛,因而老大們紛紛決定落實code review機制...
很走運,C哥負責review小二消息中心的代碼php

code review

好一段switch...case...

"小二,咱們開始吧,讓我看看前幾天你寫的代碼"。C哥微笑道。
"好的,C哥!"設計模式

小二熟練的打開電腦,找到消息中心的代碼。spa

"C哥,這是你以前告訴我用的橋接模式寫的!"
"嗯,寫的不錯,這樣抽象與實現就能各自獨立的變化了。"設計

"等等,小二,讓我看看你這段代碼是怎麼回事?"
"稍等,C哥,我放大一些。"code

小二將鼠標聚焦在這段代碼上...對象

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類。"blog

"哦,我知道了。好一段 switch...case...,但這段代碼有問題!"
"嗯?什麼問題啊?"小二好奇的問道開發

"好比我如今新增一種消息類 AppPushMessage,你想一想,你調用端的代碼是否是要改啊?"
"的確是,switch...case...這塊要變更。"rem

"對嘛,我只是新增了消息類型,不該該對調用端的代碼產生影響!"
"是,C哥。有沒有什麼辦法能夠解決這個問題呢?"get

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」便可關注。

關注「聊聊代碼」,讓咱們一塊兒聊聊「左手代碼右手詩」的事兒。

相關文章
相關標籤/搜索