簡單工廠-工廠方法-抽象工廠模式

新增一個OO設計原則:
要依賴抽象,不要依賴具體類
1 簡單工廠

  • 針對接口編程,可以隔離掉以後系統可能發生的一大堆改變。爲什麼呢?如果代碼是針對接口而寫,那麼通過多態,它可以與任何新類實現該接口。但是,當代碼使用大量的具體類時,等於是自找麻煩,因爲一旦加入新的具體類,就必須改變代碼。也就是說,你的代碼並非“對修改關閉”。想用新的具體類型來擴展代碼,必須重新打開它。所以,當遇到這樣的問題時,就應該回到OO設計原則去尋找線索。我們的第一個設計原則用來處理改變,並幫助我們“找出會變化的方面,把它們從不改變的部分分離出來“。

如下面代碼示例暴露出的問題

/**
 * @author hgl
 * @data 2018年5月6日
 * @description pizza抽象類
 */
public abstract class Pizza {

    public String name;
    //所有的比薩都需要做一些準備,如擀麪皮,醬汁,加上配料(如芝士等)
    /**
     * 麪糰
     */
    public String dough;

    /**
     * 醬汁
     */
    public String sauce;


    /**
     * 配料
     */
    public List toppings = new ArrayList();

    public void prepare(){
        System.out.println("Preparing "+name);
        System.out.println("Tossing dough...");
        System.out.println("Adding sauce...");
        System.out.println("Adding toppings:");
        for (int i = 0; i < toppings.size(); i++) {
            System.out.println(" "+toppings.get(i));
        }
    }

    //所有的比薩都會有這些動作
    /**
     * void
     * description:烤的動作
     */
    public void bake(){
        System.out.println("Bake for 25 minutes at 350");   
    }

    /**
     * void
     * description:切的動作
     */
    public void cut(){
        System.out.println("Cutting the pizza into diagonal slices");
    }

    /**
     * void
     * description:包裝的動作
     */
    public void box(){
        System.out.println("Place piaaz in official PizzaStore box");
    }

    /**
     * String
     * @return
     * description:返回這個pizza的名字
     */
    public String getName(){
        return name;
    }

}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 奶酪比薩
 */
public class Cheese extends Pizza {

    public Cheese(){
        name = "Cheese Pizza";
        dough = "Thin Crust Dough";
        toppings.add("cheese");
    }
}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 蛤蜊比薩
 */
public class Clam extends Pizza {

    public Clam(){
        name = "Clam Pizza";
    }
}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 希臘比薩
 */
public class Greek extends Pizza{

    public Greek(){
        name = "Greek Pizza";
        dough = "Thin Crust Dough";
        sauce = "Greek sauce";
        toppings.add("unique toppings");
    }
}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 比薩店
 */
public class PizzaStore {

    public Pizza orderPizza(String type){
        Pizza pizza;
        /*
          if判斷中的部分是變化的部分。隨着時間過去,比薩菜單
          會改變,比如加入新的比薩。這裏就必須一改再改。
        */
        if(type.equals("cheese")){
            pizza = new Cheese();
        }else if(type.equals("greek")){
            pizza = new Greek();
        }else if(type.equals("clam")){
            pizza = new Clam();
        }else{
            throw new RuntimeException("Error:invalid type");
        }
        /*
          下面的是不會改變的,所有的比薩必須要經過這幾個程序。
        */
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

}

現在最好將創建對象移到orderPizza()之外,但怎麼做呢?
可以把創建比薩的代碼移到另外一個對象中,由這個新對象專職創建比薩。

我們稱這個新對象爲“工廠”
工廠處理創建對象的細節,一旦有了工廠,orderPizza就變成此對象的客戶。當需要比薩時,就叫比薩工廠做一個。那些orderPizza()方法需要知道希臘比薩或者蛤蜊比薩的日子一去不復返了。現在orderPizza()方法只關心從工廠得到了一個比薩,而這個比薩實現了Pizza接口,所以它可以調用prepare(),bake(),cut(),box()來分別進行準備,烘烤,切片,裝盒。

  • 建立一個簡單比薩工廠
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 簡單比薩工廠
 */
public class SimplePizzaFactory {

    public Pizza createPizza(String type){
        Pizza pizza = null;
        if(type.equals("cheese")){
            pizza = new Cheese();
        }else if(type.equals("greek")){
            pizza = new Greek();
        }else if(type.equals("clam")){
            pizza = new Clam();
        }else{
            throw new RuntimeException("Error:invalid type");
        }
        return pizza;
    }
}

這麼做有什麼好處?似乎把一個問題搬到另外一個對象罷了
答:別忘了,SimplePizzaFactory可以有許多的客戶。雖然目前只看到orderPizza()方法是它的客戶,然而,可能還有PizzaShopMenu(比薩菜單)類會利用這個工廠來取得比薩的價錢和描述。等等。所以,把創建比薩的代碼包裝進一個類,當以後實現改變時,只需要修改這個類即可。

新的PizzaStore的代碼:

/**
 * @author hgl
 * @data 2018年5月6日
 * @description 比薩店
 */
public class PizzaStore {

    SimplePizzaFactory factory;

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

    public Pizza orderPizza(String type){
        Pizza pizza;
        pizza = factory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

}

簡單工廠其實不是一個設計模式,反而比較像是一種編程習慣,但是經常會被使用。

2 工廠方法
現在出現了很多加盟店,而我們希望確保加盟店營運的質量,但是區域的差異呢?每家加盟店都可能想要提供不同風味的比薩(比方說紐約,芝加哥,加州)。質量怎麼控制?比如加盟店採用它們自創的流程:烘烤的做法有些差異,不要切片,使用其他廠商的盒子。
我們希望建立一個框架,把加盟店和創建比薩捆綁在一起的同時又保持一定的彈性。
有個做法可讓比薩製作活動侷限於PizzaStore類,而同時又能讓這些加盟店依然可以自由地製作該區域的風味。
所要做的事情,就是把createPizza()方法放回到PizzaStore中,不過要把它設置成“抽象方法”,然後爲每個區域風味創建一個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;
    }
    /*
      現在,實例化比薩的責任被移到一個方法中,此方法就如同是
      一個“工廠”.  
      工廠方法是抽象的,所以依賴子類來處理對象的創建
      工廠方法必須返回一個產品。超類中定義的方法,通常使用到工廠方法的返回值
      工廠方法將客戶(也就是超類中的代碼,例如orderPizza())和實際創建
      具體產品的代碼分割開來。
    */
    public abstract Pizza createPizza(String type);
}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 紐約店
 */
public class NYPizzaStore extends PizzaStore{

    @Override
    public Pizza createPizza(String type) {
        /*
         * 這裏我只寫了一個紐約pizza,其實有很多
         */
        if(type.equals("cheese")){
            return new NYStyleCheesePizza();
        }else return null;
    }


}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 芝加哥店
 */
public class ChicagoStore extends PizzaStore {

    @Override
    public Pizza createPizza(String type) {
        //這裏我也只寫了一個,當然還有很多中披薩
        if(type.equals("cheese")){
            return new ChicagoStyleCheesePizza();
        }
        return null;
    }

}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 紐約風味的pizza
 */
public class NYStyleCheesePizza extends Pizza {

    public NYStyleCheesePizza(){
        /*
         * 紐約比薩有自己的大蒜番茄醬和薄餅
         */
        name = "NY style Sauce and Cheese Pizza";
        dough = "Thin Crust Dough";
        sauce = "Marinara Sauce";

        /*
         * 上面覆蓋的是意大利reggiano高級奶酪
         */
        toppings.add("Grated Reggiano Cheese");
    }
}
public class ChicagoStyleCheesePizza extends Pizza {

    public ChicagoStyleCheesePizza(){
        /*
         * 芝加哥比薩使用小番茄作爲醬料,並使用厚餅
         */
        name = "Chicago Style Deep DIsh Cheese Pizza";
        dough = "Extra Thick Crust Dough";
        sauce = "Plum Tomato Sauce";

        /*
         * 芝加哥風味的深盤比薩使用許多mozzarella(意大利白乾酪)
         */
        toppings.add("Shredded Mozzarella Cheese");
    }

    /*
     * 這個芝加哥風味比薩覆蓋類cut()方法,將比薩切成正方形
     */
    public void cut(){
        System.out.println("Cutting the pizza into square slices");
    }
}
public class PizzaTestDrive {

    public static void main(String[] args) {
        PizzaStore nyStore = new NYPizzaStore();
        PizzaStore chicagoStore = new ChicagoStore();

        Pizza pizza = nyStore.orderPizza("cheese");
        System.out.println("Ethan ordered a "+pizza.getName()+"\n");

        pizza = chicagoStore.orderPizza("cheese");
        System.out.println("Joel ordered a "+pizza.getName()+"\n");
    }
}

所有工廠模式都用來封裝對象的創建。工廠方法模式(Factory Method Pattern)通過讓子類決定該創建的對象是什麼,來達到將對象創建的過程封裝的目的。

工廠方法模式:定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到子類。

  • 依賴倒置原則
    要依賴抽象,不要依賴具體類
    即:不能讓高層組件依賴低層組件,而且不管高層或低層組件,“兩者”都應該依賴於抽象。
    所謂“高層”組件,是由其他低層組件定義其行爲的類。例如,PizzaStore是個高層組件,因爲它的行爲是由比薩定義的:PizzaStore創建所有不同的比薩對象,準備,烘烤,切片,裝盒,而比薩本身屬於低層組件。
    看一段代碼:
/*
 這個版本的PizzaStore依賴於所有的比薩對象,因爲它直接創建這些比薩對象
 如果這些類的實現改變了,那麼可能必須修改PizzaStore
 每新增一個比薩種類,就等於讓PizzaStore多了一個依賴
 因爲對於比薩具體實現的任何改變都會影響到PizzaStore。我們說PizzaStore“依賴於”比薩的實現
*/
public class DependentPizzaStore {

    public Pizza createPizza(String style,String type){
        Pizza pizza = null;
        if(style.equals("NY")){
            if(type.equals("cheese")){
                pizza = new NYStyleCheesePizza();
            }
        }else if (style.equals("Chicago")){
            if(type.equals("cheese")){
                pizza = new ChicagoStyleCheesePizza();
            }
        }else {
            System.out.println("Erro:invalid type of pizza");
            return null;
        }
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

當直接實例化一個對象時,就是在依賴它的具體類。上面的這個比薩店,它由比薩店類創建所有的比薩對象,而不是委託給工廠。
現在,這個原則告訴我們,應該重寫代碼以便於我們依賴抽象類,而不依賴具體類。對於高層及低層模塊都應該如此。
之前運用工廠方法之後,高層組件PizzaStore和低層組件(也就是這些比薩)都依賴了Pizza對象。高層組件並沒有依賴低層組件,而且兩者都依賴了抽象,低層組件依賴高層的抽象。

  • 幾個指導方針幫助你遵循此原則
    *變量不可以持有具體類的引用:如果使用new,就會持有具體類的引用。你可以用工廠來避開這樣的做法。
    *不要讓類派生自具體類:如果派生自具體類,就會依賴具體類。
    *不要覆蓋基類中已實現的方法:如果覆蓋基類已實現的方法,那麼這個基類就不是一個真正適合被繼承的抽象。基類中已實現的方法,應該由所有的子類共享。

3 抽象工廠
生產原料,但是每個區域所使用的原料都不相同,比如紐約的紅醬料和芝加哥的紅醬料是不同的。該如何做呢?一下代碼解決了這個問題


/**
 * @author hgl
 * @data 2018年5月6日
 * @description pizza抽象類
 */
public abstract class Pizza {
    public String name;
    public Dough dough;
    public Sauce sauce;
    public Veggies veggies[];
    public Cheese cheese;
    public Pepperoni pepperoni;
    public Clams clam;

    public abstract void prepare();
    public void bake(){
        System.out.println("Bake for 25 minutes at 350");
    }

    public void cut(){
        System.out.println("Cutting the pizza into diagonal slices");
    }

    public void box(){
        System.out.println("Place pizza in official PizzaStore box");
    }

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

    public String getName(){
        return name;
    }
    public String toString(){
        return name;
    }
}
public interface PizzaIngredientFactory {

    public Dough createDough();
    public Sauce createSauce();
    public Cheese CreateCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
    public Clams createClam();
}
public interface Dough {

}
public interface Sauce {

}
public interface Cheese {

}
public interface Clams {

}
public interface Pepperoni {

}
public interface Veggies {

}
public class ThinCrustDough implements Dough {

}
public class SlicedPepproni implements Pepperoni {

}
public class ReggianoCheese implements Cheese {

}
public class FreshClams implements Clams {

}
public class MarinaraSauce implements Sauce {

}
public class Mushroom implements Veggies {

}
public class Onion implements Veggies {

}
public class Garlic implements Veggies {

}
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {

    @Override
    public Dough createDough() {
        // TODO Auto-generated method stub
        return new ThinCrustDough();
    }

    @Override
    public Sauce createSauce() {
        // TODO Auto-generated method stub
        return new MarinaraSauce();
    }

    @Override
    public Cheese CreateCheese() {
        // TODO Auto-generated method stub
        return new ReggianoCheese();
    }

    @Override
    public Veggies[] createVeggies() {
        // TODO Auto-generated method stub
        Veggies[] veggies = {new Garlic(),new Onion(),new Mushroom()};
        return veggies;
    }

    @Override
    public Pepperoni createPepperoni() {
        // TODO Auto-generated method stub
        return new SlicedPepproni();
    }

    @Override
    public Clams createClam() {
        // TODO Auto-generated method stub
        return new FreshClams();
    }

}
/**
 * @author hgl
 * @data 2018年5月6日
 * @description 紐約店
 */
public class NYPizzaStore extends PizzaStore{

    @Override
    public Pizza createPizza(String type) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
        if(type.equals("cheese")){//只寫了一個例子
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("New York Style Cheese Pizza");
        }
        return pizza;
    }


}

我們引入新類型的工廠,也就是所謂的抽象工廠,來創建比薩原料家族。
通過抽象工廠所提供的接口,可以創建產品的家族,利用這個接口書寫代碼,我們的代碼將從實際工廠解藕,以便在不同上下文中實現各式各樣的工廠,製造出各種不同的產品。因爲代碼從實際的產品中解藕了,所以我們可以替換不同的工廠來取得不同的行爲。

抽象工廠模式:提供一個接口,用於創建相關或依賴對象的家族,而不需要明確指定具體類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章