1. 程式人生 > >設計模式——工廠方法 Java原始碼

設計模式——工廠方法 Java原始碼

本程式改編自《Head First Design Patterns》中的Pizza例子,我本學期早上經常吃包子。

總共有11個類:

  • 一個工廠父類 ( Creator)
  • 兩個工廠子類 ( Concrete Creator)
  • 一個包子父類 ( Product )
  • 六個包子子類 ( Concrete Product )
  • 一個Main類 ( 程式的入口 )

工廠模式
圖:ConcreteProduct 繼承 Product,ConcreteCreator 繼承 Creator,ConcreateProduct 依賴 ConcreateProduct。其實,有一條線沒有顯示出來:Creator 依賴 Product。

Head First 說,這體現了依賴倒置原則:因為高層模組(Creator)依賴抽象類(Product),底層模組(ConcreteProduct)也依賴抽象類(Product)。

補充說明:我個人感覺,嚴格的說:ConcreteProduct 和Product 之間不是依賴,而是繼承關係。勉強說的話,算作一種”非常非常強的依賴“吧。Head First : The ConcreteProduct class depends on the Product abstraction too, because they implement the Product interface (we’re using the “interface” in the general sense) in the Product abstraction class.

一個工廠父類

package factoryMethod;

public abstract class BaoZiFactory {

    // 把具體的 new操作 “下放”到子類中。
    abstract BaoZi createBaoZi(String baoZiName);

    // 這個方法不改變,即無論包子裡面的餡是什麼,製作包子的流程是一樣的(都是準備餡,然後蒸30分鐘)
    final public BaoZi makeBaoZi(String baoZiName)
    {
        BaoZi baoZi = createBaoZi(baoZiName);
        baoZi.prepare();
        baoZi.steam();

        return
baoZi; } }

兩個工廠子類

package factoryMethod;

public class BaoZiFactoryChangsha extends BaoZiFactory {

    public BaoZiFactoryChangsha() 
    {
        System.out.println("Constructor of BaoZiFactory in Changsha\n");
    }

    @Override // 依據傳進來的引數,決定new什麼包子
    BaoZi createBaoZi(String baoZiName)
    {
        BaoZi baoZi = null;
        if(baoZiName.equals("醬肉"))
        {
            baoZi = new ChangshaJiangRouBaoZi();
        }
        else if(baoZiName.equals("青菜"))
        {
            baoZi = new ChangshaQingCaiBaoZi();
        }
        else if(baoZiName.equals("鮮肉"))
        {
            baoZi = new ChangshaXianRouBaoZi();
        }
        return baoZi;
    }

}
package factoryMethod;

public class BaoZiFactoryWuhan extends BaoZiFactory {

    public BaoZiFactoryWuhan() 
    {
        System.out.println("Constructor of BaoZiFactory in Wuhan\n");
    }

    @Override // 依據傳進來的引數,決定new什麼包子
    BaoZi createBaoZi(String baoZiName) 
    {
        BaoZi baoZi = null;
        if(baoZiName.equals("醬肉"))
        {
            baoZi = new WuhanJiangRouBaoZi();
        }
        else if(baoZiName.equals("青菜"))
        {
            baoZi = new WuhanQingCaiBaoZi();
        }
        else if(baoZiName.equals("鮮肉"))
        {
            baoZi = new WuhanXianRouBaoZi();
        }
        return baoZi;
    }

}

一個包子父類

package factoryMethod;

import java.util.ArrayList;

// 父類:包子,抽象出包子共有的特性
// 有道詞典:steamed stuffed bun (蒸的,填充的,小圓麵包)
// 由於這三個單詞加起來過長,我命名放棄採納英文命名法,直接使用漢語拼音命名法BaoZi
public abstract class BaoZi {
    private String name;
    ArrayList<String> stuffings = new ArrayList<String> ();

    public void setName(String n)
    {
        this.name = n;
    }
    public String getName()
    {
        return name;
    }
    void prepare()
    {
        System.out.println("Prepare " + name);
        System.out.println("Stuffings are:");
        for(String stuff: stuffings)
        {
            System.out.println(stuff);
        }
    }

    void steam()
    {
        System.out.println("Steam for 30 minutes");
    }

    //覆蓋toString (這個方法繼承自java.lang.Object)
    public String toString()
    {
        StringBuffer display = new StringBuffer();
        display.append("---- " + name + " ----\n");
        for(String stuff : stuffings)
        {
            display.append(stuff + "\n");
        }
        return display.toString();
    }

}

六個包子子類

package factoryMethod;

public class ChangshaJiangRouBaoZi extends BaoZi {

    public ChangshaJiangRouBaoZi() 
    {
        setName("長沙醬肉包子");
        stuffings.add("辣椒");
        stuffings.add("炸醬");
        stuffings.add("肉末");
        stuffings.add("乾子");
    }
}
package factoryMethod;

public class ChangshaQingCaiBaoZi extends BaoZi {

    public ChangshaQingCaiBaoZi()
    {
        setName("長沙青菜包子");
        stuffings.add("辣椒");
        stuffings.add("包菜");
        stuffings.add("茄子");
    }

}
package factoryMethod;

public class ChangshaXianRouBaoZi extends BaoZi {

    public ChangshaXianRouBaoZi()
    {
        setName("長沙鮮肉包子");
        stuffings.add("辣椒");
        stuffings.add("鮮肉");
    }

}
package factoryMethod;

public class WuhanJiangRouBaoZi extends BaoZi {

    public WuhanJiangRouBaoZi() 
    {
        setName("武漢醬肉包子");
        stuffings.add("炸醬");
        stuffings.add("肉末");
        stuffings.add("乾子");
    }

}
package factoryMethod;

public class WuhanQingCaiBaoZi extends BaoZi {

    public WuhanQingCaiBaoZi() 
    {
        setName("武漢青菜包子");
        stuffings.add("包菜");
        stuffings.add("茄子");
    }

}
package factoryMethod;

public class WuhanXianRouBaoZi extends BaoZi {

    public WuhanXianRouBaoZi()
    {
        setName("武漢鮮肉包子");
        stuffings.add("鮮肉");
    }

}

一個Main類

package factoryMethod;

public class Main {

    public static void main(String[] args)
    {
        BaoZiFactory wuhanFactory = new BaoZiFactoryWuhan();
        BaoZiFactory changshaFactory = new BaoZiFactoryChangsha();

        BaoZi baoZi = null;

        baoZi = wuhanFactory.makeBaoZi("醬肉");
        System.out.println("Caitao made a " + baoZi.getName() + "\n");

        baoZi = wuhanFactory.makeBaoZi("青菜");
        System.out.println("Caitao made a " + baoZi.getName() + "\n");

        baoZi = changshaFactory.makeBaoZi("鮮肉");
        System.out.println("Lucy made a " + baoZi.getName() + "\n");

        baoZi = changshaFactory.makeBaoZi("青菜");
        System.out.println("Lucy made a " + baoZi.getName() + "\n");

    }

}

執行結果

直接從eclipse複製過來的

Constructor of BaoZiFactory in Wuhan

Constructor of BaoZiFactory in Changsha

Prepare 武漢醬肉包子
Stuffings are:
炸醬
肉末
乾子
Steam for 30 minutes
Caitao made a 武漢醬肉包子

Prepare 武漢青菜包子
Stuffings are:
包菜
茄子
Steam for 30 minutes
Caitao made a 武漢青菜包子

Prepare 長沙鮮肉包子
Stuffings are:
辣椒
鮮肉
Steam for 30 minutes
Lucy made a 長沙鮮肉包子

Prepare 長沙青菜包子
Stuffings are:
辣椒
包菜
茄子
Steam for 30 minutes
Lucy made a 長沙青菜包子

如果沒有工廠方法模式

又要實現同樣的功能怎麼破?程式碼如下(理想輸入條件,沒有異常處理)

public BaoZi makeBaoZi(String place, String type) 
    {
        BaoZi baoZi = null;

        if (place.equals("武漢")) 
        {
            if (type.equals("醬肉"))
            {
                baoZi = new WuhanJiangRouBaoZi();
            } 
            else if (type.equals("青菜"))
            {
                baoZi = new WuhanQingCaiBaoZi();
            }
            else if (type.equals("鮮肉")) 
            {
                baoZi = new WuhanXianRouBaoZi();
            }
        } 
        else if (place.equals("長沙")) 
        {
            if (type.equals("醬肉")) 
            {
                baoZi = new ChangshaJiangRouBaoZi();
            } 
            else if (type.equals("青菜")) 
            {
                baoZi = new ChangshaQingCaiBaoZi();
            } 
            else if (type.equals("鮮肉")) 
            {
                baoZi = new ChangshaXianRouBaoZi();
            } 
        } 

        baoZi.prepare();
        baoZi.steam();

        return baoZi;
    }
優劣之分立馬體現出來了!我們可以看到程式碼變短了(當然了,需要增加一些類作為“額外工作”,這是值得的)
建立包子的new操作“隱藏了”,取而代之的是一個factory物件呼叫createBaoZi方法

public BaoZi makeBaoZi(String place, String type) 
{
    BaoZiFactory factory = null;
    if(place.equals("武漢"))
    {
        factory = new BaoZiFactoryWuhan();
    }
    else if(place.equals("長沙"))
    {
        factory = new BaoZiFactoryChangsha();
    }

    BaoZi baoZi = factory.createBaoZi(type);
    baoZi.prepare();
    baoZi.steam();

    return baoZi;
}

1. 工廠方法模式的優勢:如果位於武漢的工廠需要增加一種口味的包子(這種類似的事情經常發生),比如”熱幹包子“,那麼工廠方法模式只用增加一個包子子類,然後修改武漢工廠子類就行了。

關鍵是下面的程式碼不變,放到哪裡都一樣,以不變應萬變!

BaoZiFactory factory = null;
if(place.equals("武漢"))
{
    factory = new BaoZiFactoryWuhan();
}
else if(place.equals("長沙"))
{
    factory = new BaoZiFactoryChangsha();
}
BaoZi baoZi = factory.createBaoZi(type);

2. 沒有工廠方法模式的劣勢:如果位於武漢的工廠需要增加一種口味的包子(這種類似的事情經常發生),比如”熱幹包子“,那麼所有用於在武漢建立包子的
if( … ) { new … }
else( ) { … }
的程式碼都在後面增加一個
if( 熱幹 ) {new ReGanBaoZiWuhan() }
else( ) { … }

BaoZi baoZi = null;

        if (place.equals("武漢")) 
        {
            if (type.equals("醬肉"))
            {
                baoZi = new WuhanJiangRouBaoZi();
            } 
            else if (type.equals("青菜"))
            {
                baoZi = new WuhanQingCaiBaoZi();
            }
            else if (type.equals("鮮肉")) 
            {
                baoZi = new WuhanXianRouBaoZi();
            }
            *********增加的程式碼*****************
            else if(type.equals("熱幹"))
            {
                baoZi = new WuhanReGanBaoZi();
            }
            ************************************
        } 
        else if (place.equals("長沙")) 
        {
            if (type.equals("醬肉")) 
            {
                baoZi = new ChangshaJiangRouBaoZi();
            } 
            else if (type.equals("青菜")) 
            {
                baoZi = new ChangshaQingCaiBaoZi();
            } 
            else if (type.equals("鮮肉")) 
            {
                baoZi = new ChangshaXianRouBaoZi();
            } 
        } 

改一個地方還比較輕鬆,但是關鍵在於很有可能在很多地方都要用到包子。假設100個地方都要建立包子,那麼100個地方的if( … )else( )“的程式碼都要修改!這完全是反人類,違反了開閉原則。