1. 程式人生 > >HeadFirst設計模式讀書筆記之工廠模式

HeadFirst設計模式讀書筆記之工廠模式

1. 簡單工廠

1. 你開了一家披薩店,點披薩的方法可能是這樣:

    public Pizza orderPizza(String type) {
        Pizza pizza;
        if (type.equals("芒果披薩")){
            pizza = new MangoPizza();
        }else if (type.equals("核桃披薩")){
            pizza = new WalnutPizza();
        }else if (type.equals("橙皮披薩")){
            pizza = new FlavedoPizza();
        }else {
            pizza = new StarPizza();
        }

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

可以看到,每當你想增加一種披薩型別,就要修改程式碼,新增一種if else條件.當有多個系統存在orderPizza的需求時,每個系統都要同時修改他們的程式碼.因此,需要將這種例項化具體物件的程式碼封裝起來.

public class PizzaStore {

    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory){
        this.factory = factory;
    }

    public Pizza orderPizza(String type) {
        Pizza pizza;
        pizza = factory.createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}
public class SimplePizzaFactory {

    public Pizza createPizza(String type){
        Pizza pizza = null;
        
        if (type.equals("芒果披薩")){
            pizza = new MangoPizza();
        }else if (type.equals("核桃披薩")){
            pizza = new WalnutPizza();
        }else if (type.equals("橙皮披薩")){
            pizza = new FlavedoPizza();
        }else {
            pizza = new StarPizza();
        }
        
        return pizza;
    }
}

這就是簡單工廠方法,他不算一種設計模式,而是一種程式設計習慣.其要點有二:

1. 將產品定義為抽象類,可以被覆蓋

2. 將具體的產品放到工廠類中來例項化,由工廠建立並返回給客戶,這樣便使得產品的建立邏輯可以解耦合,並增加了複用性

2. 工廠方法

1. 假設你現在有了很多加盟店,每種加盟店根據地區的差異有自己的工廠,但加盟店雖然採用工廠方法建立披薩,但其他部分卻有所不同:烘烤的做法,不切片,使用雜七雜八的盒子...

這時,你想把建立披薩的方法與每家披薩店繫結在一起,讓每家PizzaStore中都使用createPizza()建立pizza,並且這些店還可以擁有一定的製作披薩的自主權.因此,我們需要把PizzaStore和createPizza()定義成抽象的

/**
 * 抽象的PizzaStore
 */
public abstract class PizzaStore {

    public Pizza orderPizza(String type) {
        Pizza pizza;
        // 使用自己的方法做披薩
        pizza = createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    // 將做披薩的工廠方法放到PizzaStore中,工廠方法是抽象的,所以依賴子類來處理物件的建立
    public abstract Pizza createPizza(String type);
}

2. 現在由PizzaStore的子類來具體賣披薩

public class BeijingPizzaStore extends PizzaStore {
    @Override
    public Pizza createPizza(String item) {
        if (item.equals("橙皮披薩")){
            return new FlavedoPizza();
        }else {
            return new PekingDuckPizza();
        }
    }
}
public class ChengduPizza extends PizzaStore {
    @Override
    public Pizza createPizza(String type) {
        if (type.equals("芒果披薩")){
            return new MangoPizza();
        }else {
            return new StarPizza();
        }
    }
}

現在,orderPizza()與具體的Pizza實現了一定的解耦合,決定最終Pizza物件的變成了某個具體的pizza店,而pizza店則由顧客來決定

3. 一些對工廠方法的理解:

1. 工廠方法是抽象的,所以依賴子類來建立物件,這樣便將超類的程式碼與具體建立產品的程式碼分割開了

2. 工廠方法必須返回一個產品,超類通常中會用到這個產品

3. 工廠方法將產品與建立者抽象為介面,使他們成為平級的存在,具體的產品建立則使用子類來互動

4. 工廠方法將產品例項化的操作推遲到子類,子類的選擇則由建立者來決定

5. 簡單工廠方法更趨向組合,工廠方法則利用抽象和繼承

6. 簡單工廠封裝了物件的建立,一個工廠處理所有的產品,但對同一個產品不可變更其做法,工廠方法則更有彈性,子類通過重寫可以更改產品的建立

3. 依賴倒置原則

下面是一個簡單粗暴的披薩店:

/**
 * 依賴具體物件的PizzaStore
 */
public class PizzaStore {
    
    public Pizza createPizza(String style, String type){
        Pizza pizza = null;
        if (style.equals("一元檔")){
            if (type.equals("芒果披薩")){
                pizza = new MangoPizza();
            }else if (type.equals("核桃披薩")){
                pizza = new WalnutPizza();
            }
        }else if (style.equals("二元檔")){
            if (type.equals("北京烤鴨披薩")){
                pizza = new PekingDuckPizza();
            }else if (type.equals("橙皮披薩")){
                pizza = new FlavedoPizza();
            }
        }else {
            System.out.println("披薩賣完了");
        }
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

可以看到,這個披薩店依賴於每一個具體的披薩物件,每增加一個披薩就要修改一次程式碼,增加一個依賴,可以說是非常不好用了

從圖中看到,高層元件直接依賴於低層元件而依賴倒置原則的的內容就是:

要依賴抽象,不要依賴具體類
這個原則說明不管高層還是低層元件都不應該依賴具體類,而應該依賴於抽象.如圖所示:

與之前相比高層元件PizzaStore不在直接依賴於具體的Pizza物件,而是依賴於Pizza介面,而具體的Pizza產品則由被高層元件依賴顛倒成了依賴Pizza介面,這就是一種依賴倒置.總體來看,PizzaStore和Pizza都抽象化了,不用再依賴於具體的型別,符合依賴倒置的設計原則.
有三個指導方針可以讓你更好地遵守依賴倒置原則:

  • 變數不可以持有具體類的引用
  • 不要讓類派生自具體類
  • 不要覆蓋基類中已覆蓋的方法

4. 抽象工廠方法:建立產品家族

與普通的工廠方法比較,抽象工廠方法要顯得重型得多: 工廠方法只建立一種產品,抽象工廠方法則建立一個家族的產品

1. 典型做法是建立一個工廠的介面,這個介面負責建立所有的產品家族成員,如建立一個生產調料家族的工廠:

/**
* 原料工廠的介面,實現這樣的介面,可以隨意組合產出各式各樣的調料
* 每個原料都是一個類
*/
public interface SeasoningFactory {

   Chili createChili();
   
   DouBan createDouBan();
   
   Pepper createPepper();
   
   Salt createSalt();

}
/**
 * 郫縣調料生產廠
 */
public class PiXianSeasoningFactory implements SeasoningFactory {

    /**
     *  虎皮青椒
     * @return
     */
    @Override
    public Chili createChili() {
        return new HuPiQingJiao();
    }

    /**
     * 郫縣豆瓣
     * @return
     */
    @Override
    public DouBan createDouBan() {
        return new PiXianDouBan();
    }

    /**
     * 花椒油
     * @return
     */
    @Override
    public Pepper createPepper() {
        return new HuaJiaoYou();
    }

    /**
     * 食用鹽
     * @return
     */
    @Override
    public Salt createSalt() {
        return new ShiYongYan();
    }
}
/**
 * 灌縣調料生產廠
 */
public class GuanXianSeasoningFactory implements SeasoningFactory {

    /**
     *  虎皮青椒
     * @return
     */
    @Override
    public Chili createChili() {
        return new HuPiQingJiao();
    }

    /**
     * 老乾媽豆瓣醬
     * @return
     */
    @Override
    public DouBan createDouBan() {
        return new LaoGanMaDouBanJiang();
    }

    /**
     * 花椒油
     * @return
     */
    @Override
    public Pepper createPepper() {
        return new HuaJiaoYou();
    }

    /**
     * 食用鹽
     * @return
     */
    @Override
    public Salt createSalt() {
        return new ShiYongYan();
    }
}

下面是抽象工廠方法的類圖:

從類圖中可以看出來,抽象工廠方法有一個缺點,就是當添加了一個新產品介面時,要去抽象工廠介面中新增一個方法,這會造成一些麻煩

2. 工廠方法與抽象工廠的一些對比

相同點:

  • 都是負責建立物件
  • 都能使程式解耦,將程式從建立大量具體物件的泥潭中拉出來

不同點:

  • 工廠方法用子類來建立具體物件,利用抽象和繼承.抽象工廠包含了一大堆介面,利用組合的思想來建立物件
  • 工廠方法一般用來建立一種產品,抽象工廠則建立一族產品
  • 工廠方法功能簡單,使用輕便.抽象工廠功能強大,使用時會建立大量的類

    3. 下面使用抽象工廠生產的原料來製作披薩

/**
 * 原料工廠的介面,實現這樣的介面,可以隨意組合產出各式各樣的調料
 * 每個原料都是一個類
 */
public interface SeasoningFactory {

    /**
     * 辣椒
     * @return
     */
    Chili createChili();

    /**
     * 豆瓣
     * @return
     */
    DouBan createDouBan();

    /**
     * 花椒
     * @return
     */
    Pepper createPepper();

    /**
     * 鹽
     * @return
     */
    Salt createSalt();

}
/**
 * 豆瓣
 */
public abstract class DouBan {
    public abstract String sayMyName();
}
public class PiXianDouBan extends DouBan {
    @Override
    public String sayMyName() {
        return "郫縣豆瓣";
    }
}
public class LaoGanMaDouBanJiang extends DouBan {
    @Override
    public String sayMyName() {
        return "老乾媽豆瓣醬";
    }
}
/**
 * 郫縣調料生產廠
 */
public class PiXianSeasoningFactory implements SeasoningFactory {

    /**
     *  虎皮青椒
     * @return
     */
    @Override
    public Chili createChili() {
        return new HuPiQingJiao();
    }

    /**
     * 郫縣豆瓣
     * @return
     */
    @Override
    public DouBan createDouBan() {
        return new PiXianDouBan();
    }

    /**
     * 花椒油
     * @return
     */
    @Override
    public Pepper createPepper() {
        return new HuaJiaoYou();
    }

    /**
     * 食用鹽
     * @return
     */
    @Override
    public Salt createSalt() {
        return new ShiYongYan();
    }
}
/**
 * 灌縣調料生產廠
 */
public class GuanXianSeasoningFactory implements SeasoningFactory {

    /**
     *  虎皮青椒
     * @return
     */
    @Override
    public Chili createChili() {
        return new HuPiQingJiao();
    }

    /**
     * 老乾媽豆瓣醬
     * @return
     */
    @Override
    public DouBan createDouBan() {
        return new LaoGanMaDouBanJiang();
    }

    /**
     * 花椒油
     * @return
     */
    @Override
    public Pepper createPepper() {
        return new HuaJiaoYou();
    }

    /**
     * 食用鹽
     * @return
     */
    @Override
    public Salt createSalt() {
        return new ShiYongYan();
    }
}
/**
 * 每一個披薩都有一組抽象工廠生產的原料
 *
 */
public abstract class Pizza {

    protected String name;

    /**
     * 辣椒
     */
    protected Chili chili;

    /**
     * 豆瓣
     */
    protected DouBan douBan;

    /**
     * 花椒
     */
    protected Pepper pepper;

    /**
     * 鹽
     */
    protected Salt salt;


    /**
     * 在這個方法中,收集抽象工廠生產的原料
     */
    public abstract void prepare();

    public void bake(){
        System.out.println("烘烤...");
    }

    public void cut(){
        System.out.println("切片...");
    }

    public void box() {
        System.out.println("包裝...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString(){
         return "披薩製作完畢:"+name
        +"\n生產原料:"+chili.sayMyName()
                +"\t"+douBan.sayMyName()
                +"\t"+pepper.sayMyName()
                +"\t"+salt.sayMyName();

    }
}
public class ChengduPizza extends Pizza{

    // 調料工廠
    SeasoningFactory seasoningFactory;

    public ChengduPizza(SeasoningFactory seasoningFactory) {
        this.name = "舊成都披薩";
        this.seasoningFactory = seasoningFactory;
    }

    @Override
    public void prepare() {
        System.out.println("開始準備 "+getName()+":");
        chili = seasoningFactory.createChili();
        douBan = seasoningFactory.createDouBan();
        pepper = seasoningFactory.createPepper();
        salt = seasoningFactory.createSalt();
        System.out.println("以下原料準備完畢:"+chili.sayMyName()
        +","+douBan.sayMyName()+","+pepper.sayMyName()+","+salt.sayMyName());
    }
}
public abstract class PizzaStore {

    public abstract Pizza createPizza(String type);
}
public class StarBoundPizzaStore extends PizzaStore {
    @Override
    public Pizza createPizza(String type) {
        Pizza pizza;
        SeasoningFactory factoryA = new PiXianSeasoningFactory();
        SeasoningFactory factoryB = new GuanXianSeasoningFactory();

        if (type.equals("舊成都披薩")){
            pizza = new ChengduPizza(factoryA);;
        }else if (type.equals("新北京披薩")){
            pizza = new PekingPizza(factoryB);
        }else {
            pizza = new ChengduPizza(factoryB);
        }
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }

    public static void main(String[] args) {
        PizzaStore pizzaStore = new StarBoundPizzaStore();
        Pizza pizza = pizzaStore.createPizza("舊成都披薩");
        System.out.println(pizza.toString());
        Pizza pizza1 = pizzaStore.createPizza("新北京披薩");
        System.out.println(pizza1.toString());
    }
}
結果:
開始準備 舊成都披薩:
以下原料準備完畢:虎皮青椒,郫縣豆瓣,花椒油,食用鹽
烘烤...
切片...
包裝...
披薩製作完畢:舊成都披薩
生產原料:虎皮青椒   郫縣豆瓣    花椒油 食用鹽
開始準備 新北京披薩:
以下原料準備完畢:虎皮青椒,老乾媽豆瓣醬,花椒油,食用鹽
烘烤...
切片...
包裝...
披薩製作完畢:新北京披薩
生產原料:虎皮青椒   老乾媽豆瓣醬  花椒油 食用鹽