AD

Design Pattern - Factory (一)

.introduction

        Factory模式是很常見的模式, 架構有很多種, 但是不管哪一種, 其基本的訴求都是一樣的, 在Design Patterns一書中定義了Factory模式的目的.

Factory Method
        定義可資生成物件的介面, 但讓子類別去決定該具現出哪一種類別的物件. 此模式讓類別將具現化程序交付給子類別去處置.

        上面的Factory模式目的乍看之下似乎很單純不複雜, 不過到軟體實作面又會是什麼情形, 以下本篇文章對Factory模式做一個簡單的講解.



.Thinking in Factory Pattern

         在軟體設計時常會碰到一個主物件裡面需要產生多種副物件的情形, 以下簡單舉個例子來做說明, 假設我們今天實做幾種商店菜單, 商品種類有以下幾種.

商店商品
  • 蘋果汁
  • 柳橙汁
  • 檸檬汁

        針對這個需求, 你可能會寫出以下這段程式.

商店菜單的程式碼
#include <iostream>
#include <string>

using namespace std; 

//=======果汁=======
class Juice
{
public:
    Juice(){}

//method
public:
    virtual void Drink()=0;
};

class OrangeJuice : public Juice
{
public:
    OrangeJuice(){}
//method
public:
    virtual void Drink()
    {
        cout<<"Drink Orange Juice"<<endl;
    }
};

class AppleJuice : public Juice
{
public:
    AppleJuice(){}
//method
public:
    virtual void Drink()
    {
        cout<<"Drink Apple Juice"<<endl;
    }
};

class LemonJuice : public Juice
{
public:
    LemonJuice(){}
//method
public:
    virtual void Drink()
    {
        cout<<"Drink Lemon Juice"<<endl;
    }
};
//==============

//=======菜單=======

//第二家商店
class ShopMenuA
{
public:
        ShopMenuA(){};

//method
public:
    Juice* Order(string prodName)
    {
        if(prodName == "Apple_juice")
            return new AppleJuice();
        //!!!!!!!!!!!!!
        //新增飲料
        /*
        else if(prodName == "Custom_juice")
        return new CustomJuice();
        */
        return NULL;

    }
};

//第二家商店
class ShopMenuB
{
public:
        ShopMenuB(){};

//method
public:
    Juice* Order(string prodName)
    {
        if(prodName == "Orange_juice")
            return new OrangeJuice();
        else if(prodName == "Apple_juice")
            return new AppleJuice();
        else if(prodName == "Lemon_juice")
            return new LemonJuice();
        //!!!!!!!!!!!!!
        //新增飲料
        /*
        else if(prodName == "Custom_juice")
        return new CustomJuice();
        */
        return NULL;

    }
};
//==============

int main(int argc, _TCHAR* argv[])
{
    ShopMenuA _menuA;
    ShopMenuB _menuB;
    //點飲料
    Juice *juice = _menuA.Order("Apple_juice");

    if(juice)
    {
        juice->Drink();

        delete juice;
        juice=NULL;
    }

    juice = _menuB.Order("Lemon_juice");

    if(juice)
    {
        juice->Drink();

        delete juice;
        juice=NULL;
    }

    return 0;
}



        上段單看ShopMenu架構乍看之下功能沒有問題, 不過當你獲得新的需求要作修改的時候, 麻煩的問題就出現了, 如程式碼中所看到的, 至少可以列出兩點很明顯可以看得出來的問題.

  1. 要新增一個產品, 就必須修改ShopMenu物件的架構, 這意味著每次新增刪產品就免不了修改架構.
  2. 要新增一個新的商店菜單時, 可以看到這個架構裡產生了大量重複的程式碼, 違背了程式碼重用規則.

        針對介面實作, 以及適當的封裝, 可以降低資料的耦合性(coupling), 使物件的彈性更佳, 針對這種問題, 如何作修改可以把新增產品從架構移到執行期階層, 從上面的程式碼可以看出ShopMenu中商品的產生是最主要會變動的部分, 我們試著把變動的部分封裝出來, 修改部分程式碼如下.

封裝變動部分後的程式碼
class JuiceFactoryA : public JuiceFactory
{
public:
    JuiceFactoryA(){}

//method
public:
    virtual Juice* CreateJuice(string prodName)
    {
        if(prodName == "Apple_juice")
            return new AppleJuice();
        //!!!!!!!!!!!!!
        //新增飲料
        /*
        else if(prodName == "Custom_juice")
        return new CustomJuice();
        */
        return NULL;
    }
};

class JuiceFactoryB : public JuiceFactory
{
public:
    JuiceFactoryB(){}

//method
public:
    virtual Juice* CreateJuice(string prodName)
    {
        if(prodName == "Orange_juice")
            return new OrangeJuice();
        else if(prodName == "Apple_juice")
            return new AppleJuice();
        else if(prodName == "Lemon_juice")
            return new LemonJuice();

        return NULL;
    }
};

class ShopMenu
{
public:
        ShopMenu(JuiceFactory *factory)
        {
            m_JuiceFactory = factory;
        };

//method
public:
    Juice* Order(string prodName)
    {
        if(m_JuiceFactory)
        {
            return m_JuiceFactory->CreateJuice(prodName);
        }        
        return NULL;
    }
public:
    JuiceFactory *m_JuiceFactory;
};


int main(int argc, _TCHAR* argv[])
{
    JuiceFactoryA _factoryA;
    JuiceFactoryA _factoryB;

    //A Shop
    ShopMenu _menuA(&_factoryA);

    //B Shop
    ShopMenu _menuB(&_factoryB);

    //點飲料
    Juice *juice = _menuA.Order("Apple_juice");

    if(juice)
    {
        juice->Drink();

        delete juice;
        juice=NULL;
    }

    juice = _menuB.Order("Lemon_juice");

    if(juice)
    {
        juice->Drink();

        delete juice;
        juice=NULL;
    }
    return 0;
}



從上面的程式碼可以看出一些主要的改變, 以下大致列出.

  1. 商店的產品決定權由ShopMenu中移到執行期階層,由丟入的Factory種類決定.
  2. ShopMenu程式碼變成可重用(Reuse), 不需要設計兩個不同的class ShopMenu, 彈性較佳.

        雖然新增飲料部分仍有重複的程式碼, Factory的部分仍有很大的修改空間, 但是可以看出單單只是把變動的部分封裝出來, 已經讓程式碼結構變乾淨許多, 以這種方式為主要精神, 在諸如此類的處理中, Factory模式以封裝, 委派等概念提供一些良好的架構, 把這些物件封裝起來處理, 以下本篇先講解Factory模式的第一種Simple Factory.


.Implement

        Simple Factory架構相當簡單,整體來說與上面的例子相像, 主要就是把建立物件的部分直接封裝出來處理,其架構如下.

Simple Factory

    
        以上段的例子ShopMenu來看,架構如下.

ShopMenu Simple Factory


ShopMenu Simple Factory(Simple Version)

程式碼的實作與上面例子是一樣的, 此處就不再列出, 詳細可以參考上面 - 封裝變動部分後的程式碼.


.結論

        如此設計有什麼好處呢, 如同在之前解說的那樣, Simple Factory把產品的產生從軟體架構階段移到執行階段, 舉例來說, 當我們要產生一個新的ShopMenu時, 只需要設計一個新的Factory, 在執行期加上去即可, 甚至ShopMneu的產品可在執行期進行抽換, 在這種使用需求下, 優點是很顯著的.


.Related

Design Pattern - Singleton

Design Pattern - Observer

Design Pattern Practice Example - Log(Observer, Singleton)

沒有留言:

張貼留言