設計模式之三種工廠模式

青風百里發表於2019-02-20

剛剛學設計模式,不知道理解是否正確,應該有很多不對的地方,請大家指點。

簡單工廠模式

問題背景

大眾集團要生產汽車,但是不確定要生產哪幾個品牌的汽車。

角色扮演

  • 車: VwCar 類、AudiCar
  • 工廠: Factory
  • 客戶端:需要去例項化車的人或者其它程式碼

適用場景

要生產的產品的數目和型別未知的時候。例如你的專案經理今天突然說:"需要實現一個匯出為PDF的的功能"。

實現

class VwCar
{
}
class AudiCar
{
}

工廠

class Factory
{
    //這兩個常量用於給客戶端一個友好的提示,不要也行
    const VW = 1;
    const AUDI = 2;

    public function produce($type)
    {
        switch ($type) {
            case self::VW:
                return new VwCar();
                break;
            case self::AUDI:
                return new AudiCar();
                break;
        }
    }
}

客戶端

$factory = new Factory();
$vw = $factory->produce(Factory::VW); //生產大眾車
var_dump($vw); //輸出 object(VwCar)#3 (0) { }

可以看到,生產了一輛大眾車。這個時候,如果還需要生產保時捷,那麼增加一條產品線,稍微改改工廠即可。

class Factory
{
    //這三個常量用於給客戶端一個友好的提示,不要也行
    const VW = 1;
    const AUDI = 2;
    const PORSCHE = 3; // <----增加了一個常量

    public function produce($type)
    {
        switch ($type) {
            case self::VW:
                return new VwCar();
                break;
            case self::AUDI:
                return new AudiCar();
                break;
            case self::PORSCHE:  //  <----增加了一條產品線,用於生產保時捷
                return new PorscheCar();
                break;
        }
    }
}
  • 優點
    • 增加產品的時候,只需要修改 Factory 類,增加相應的邏輯即可。
  • 需要改進的地方
    • 在工廠類的內部用了 switch 語句,用於判斷 new 什麼物件(生產什麼車),這就是耦合的表現。

當你發現在你的程式碼中使用了大量的條件語句,不妨考慮使用多型來解決問題。

工廠方法模式

為啥叫工廠方法模式,叫啥名不重要,捋清楚邏輯才是關鍵。

問題背景

基於上文簡單工廠模式中提到的需要改進的地方,得想辦法解決。如果我還要生賓利、布加迪,我不得不去修改工廠方法中的程式碼,但是我並不想這樣做。

角色扮演

  • 車: VwCar 類、AudiCar
  • 總工廠: Factory 抽象類,或者介面也行
  • 子工廠: VwCarFactory 類、 AudiCarFactory
  • 客戶端:需要去例項化車的人或者其它程式碼

適用場景

要生產的產品的數目和型別未知的時候。

實現

class VwCar
{
}
class AudiCar
{
}

總工廠

abstract class Factory
{
    abstract public function produce();
}

子工廠

class VwCarFactory extends Factory
{
    public function produce()
    {
        return new VwCar();
    }
}

class AudiCarFactory extends Factory
{
    public function produce()
    {
        return new AudiCar();
    }
}

客戶端

$factory = new VwCarFactory(); //例項化大眾車工場
$vw = $factory->produce(); //生產大眾車
var_dump($vw); //輸出 object(VwCar)#3 (0) { }

可以看到,生產了一輛大眾車,不同於簡單工廠的是,這時是使用大眾車的工廠去生產大眾車,煩人的條件語句沒有了。且作為客戶端,我不需要關心 $factory->produce() 的具體實現,或者說不需要關心你這車怎麼生產的,你把車給我就好。

如果還需要生產保時捷,那麼新建一個工廠即可。

class PorscheCarFactory extends Factory
{
    public function produce()
    {
        return new PorscheCar();
    }
}

利用多型,消除了煩人的條件語句。

  • 優點
    • 增加產品的時候,只需要增加對應的工廠類。
  • 需要改進的地方
    • 只能橫向擴充套件,無法縱向擴充套件。比如說,大眾集團決定對每種車生產低端車和高階車。

讀到這裡,你可能感覺我在扯犢子,我隱約感覺到我確實是在扯犢子,可能扯著扯著就整明白了。

抽象工廠模式

問題背景

為了解決上文提到的,只能"橫向"擴充套件,無法"縱向"擴充套件的問題。這裡引出一個概念,產品族。我並不想針對每一個系列的車都去建一個工廠,畢竟建工廠要很多錢,我希望建一個奧迪車工廠就可以生產低端奧迪和高階奧迪,將來可能還會有中端、頂端等等。

無論是低端奧迪還是高階奧迪,都是奧迪車。

角色扮演

  • 低端車介面: LowEndCar 類(生產低端車就要有低端車的模板,例如廠家規定低端車不端備八向電動座椅調節)
  • 車: VwLowEndCar 類(低端大眾)
  • 總工廠: Factory 抽象類,或者介面也行
  • 子工廠: VwCarFactory
  • 客戶端:需要去例項化車的人或者其它程式碼

適用場景

要生產的產品的數目和型別未知的時候。與工廠方法類似,是高階版的"工廠方法"。

實現(基本款)

低端車介面

interface LowEndCar
{
    //規定一些低端車必須實現的方法,例如低端車必須不端備八向電動座椅調節
}

大眾低端車

class VwLowEndCar implements LowEndCar
{
}

總工廠

abstract class Factory
{
    abstract public function produceLowEndCar();
}

大眾車的子工廠

class VwCarFactory extends Factory
{
    public function produceLowEndCar()
    {
        return new VwLowEndCar();
    }
}

客戶端

$factory = new VwCarFactory(); //例項化大眾車工廠
$VwLowEndCar = $factory->produceLowEndCar(); //生產大眾低端車
var_dump($VwLowEndCar); //輸出 object(VwLowEndCar)#3 (0) { }

此時廠家說要生產高階車,沒問題,那麼需要做幾個事情呢?

實現(生產高階車)

第一,給出高階車的介面。

高階車介面

interface HeightEndCar
{
    //規定一些高階車必須實現的方法,例如高階車必須端備座椅加熱
}

第二,給出具體的大眾高階車。

大眾高階車

class VwHeightEndCar implements HeightEndCar
{
}

第三,在總工廠中給出生產高階車的方法。

總工廠

abstract class Factory
{
    abstract public function produceLowEndCar();
    abstract public function produceHeightEndCar();
}

第四,在具體的大眾車工廠中去實現生產高階車的方法。

大眾車的子工廠

class VwCarFactory extends Factory
{
    public function produceLowEndCar()
    {
        return new VwLowEndCar();
    }
    public function produceHeightEndCar()  // <----可以生產高階車了
    {
        return new VwHeightEndCar();
    }
}

第五,客戶端呼叫。

客戶端

$factory = new VwCarFactory(); //例項化大眾車工廠
$VwHeightEndCar = $factory->produceHeightEndCar(); // 生產大眾高階車
var_dump($VwHeightEndCar); //輸出 object(VwHeightEndCar)#3 (0) { }

看到這裡,需要回頭想想,這樣做有啥好處?

這個時候,如果我們要增加產品線,就很輕鬆愉悅了。至少不用去修改總工廠的某些具體方法,雖然本例中總工廠中沒有任何具體的方法,但是完全可以加入一些,例如無論是高階車低端車,總得給出車的長度吧。

abstract class Factory
{
    abstract public function produceLowEndCar();
    abstract public function produceHeightEndCar();
    public function getWidth()
    {
        return '獲取車的長度';
    }
}

這裡的 getWidth() ,無論哪一個具體的子工廠都可以共享,豈不是輕鬆愉悅。

實現(生產奧迪車)

還是要考慮,要做哪些事情?

第一,給出奧迪車。

class AudiLowEndCar implements LowEndCar
{
}

class AudiHeightEndCar implements HeightEndCar
{
}

第二,給出奧迪車的子工廠。

class AudiCarFactory extends Factory
{
    public function produceLowEndCar()
    {
        return new AudiLowEndCar();
    }
    public function produceHeightEndCar()
    {
        return new AudiHeightEndCar();
    }
}

第三,客戶端呼叫。

$factory = new AudiCarFactory(); //例項化奧迪車工廠
$AudiLowEndCar = $factory->produceLowEndCar(); // 生產奧迪低端車
$AudiHeightEndCar = $factory->produceHeightEndCar(); // 生產奧迪高階車
var_dump($AudiLowEndCar); //輸出 object(AudiLowEndCar)#3 (0) { }
var_dump($AudiHeightEndCar); //輸出 object(AudiHeightEndCar)#4 (0) { }

思考

如果現在廠家要生產中端車,那些地方要動刀子?

  • 需要6刀
    1. 中端車介面
    2. 中端大眾車去實現中端車介面
    3. 中端奧迪車去實現中端車介面
    4. 總工廠增加抽象的建立中端車的方法
    5. 大眾的子工廠去實現總工廠中建立中端車的方法
    6. 奧迪的子工廠去實現總工廠中建立中端車的方法

青風百里

相關文章