1. 程式人生 > >抽象工廠模式詳解 —— head first 設計模式

抽象工廠模式詳解 —— head first 設計模式

專案例項

假設你有一家 pizza 店,你有很多種 pizza,要在系統中顯示你所有 pizza 種類。實現這個功能並不難,使用普通方式實現:

public class PizzaStore {
     Pizza orderPizza(String type) {
       Pizza pizza = null;
       if (type.equals("cheese")) {
         pizza = new CheesePizza();
       } else if (type.equals("clam")) {
           pizza = new ClamPizza();
       } else if (type.equals("veggie")) {
         pizza = new VeggiePizza();
       }
     pizza.prepare();
     pizza.bake();
     pizza.cut();
     pizza.box();
     return pizza;
   }
}

這種把選擇披薩和製作過程全放在一起,如果新推出一款披薩就要修改 OrderPizza 方法,不符合開閉原則。

我們可已經這段變化的程式碼移到一個專門的類中,這個類只管建立 Pizza,我們將這個類的物件稱之為“工廠”;

簡單工廠(factory)

根據“將變化抽離並封裝“的原則,我們可以將建立 pizza 例項這一塊給抽離出來,因為這塊後邊可能會新增一些別的 pizza 型別,由一個物件來專職建立 pizza 物件。如果有一個 SimplePizzaFactory,那麼 orderPizza() 就變成此物件的客戶;

當客戶下單,pizzaStore 呼叫 orderPizza() 的時候,就叫比薩工廠做一個,orderPizza() 只關心從工廠得到一個比薩;

public class SimplePizzaFactory {
        public PizzaCreatePizza(string pizzaType){
            Pizza pizza = null;
            if(pizzaType.Equals("cheese"))
                pizza = newCheesePizza();
            else if (pizzaType.Equals("pepperoni"))
                pizza = newPepperoniPizza();
            else if(pizzaType.Equals("clam"))
                pizza = newCalmPizza();
            else if(pizzaType.Equals("veggie"))
                pizza = newVeggiePizza();
            return pizza;
        }
    }

接下來看下 Pizza 類:

public class Pizza {
        Pizza OrderPizza(stringpizzaType){
            Pizza pizza;
            SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory();//生成pizza
            pizza =simplePizzaFactory.CreatePizza(pizzaType);
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            return pizza;
        }
}

這樣的話,就可以遵循了把變化的部分抽象成一個類。下次需要變化,只需要對變化的類的部分做修改即可。

下面給出其UML圖。

簡單工廠的目的,主要負責實現生產物件的細節,根據訂單來生產。每一個商店有對應著自己的工廠。在此,可以將 OrderPizza 是工廠的一個客戶,當然還可以是其他類似的方法,比如 PizzaShopMenu 也可以是工廠的客戶。客戶就是為了獲取工廠生成的物件。前者是為了賣給 people,後者是為了使用 Pizza 類獲取其描述和價格等。這樣遵循了找出變化的部分,將其封裝到一個類中的原則,從而到達了複用的目的。

簡單工廠之所以簡單是因為它只根據需要,來生產出來指定的 Pizza。

特點:所有的產品都由一個工廠來生產。

上面的簡單工廠並不是一個真正的模式,只是一種程式設計習慣,這個不能算工廠模式,不過也算是基本滿足需求。

接下來我們來看看工廠方法的實現。

工廠方法

背景更新:

假如現在你要開分店,各種加盟商進來後,他們都要開發符合本地口味的 pizza,那就需要各個地方都有一個工廠,也就是每個地方繼承 SimpleFactory類,但是每個工廠並不是完全使用你原來的烘培方法。或許,我們可以像 1 中那樣利用簡單的工廠,對應不同地區的加盟店建立不同的工廠。

這樣做導致的另一個問題就是,不同的加盟店披薩的製作流程、方法可能不同,如何才能把加盟店和建立披薩捆綁在一起的同時又保持一定的彈性?

我們可以把 CreatePizza()方法放回到 PizzaStore 中,但是要把它設定為抽象方法,然後為每一個區域加盟店建立一個 PizzaStore 的子類。

如下所示:

 

 

 對應的程式碼如下:

public abstract class PizzaStore  {
        public PizzaOrderPizza(string pizzaType) {
            Pizza pizza =CreatePizza(pizzaType);
            pizza.Prepare();
            pizza.Bake();
            pizza.Cut();
            pizza.Box();
            return pizza;
        }
 
        public abstract Pizza CreatePizza(string pizzaType); //把工廠物件移到該方法中,該方法為抽象方法
   }

 對應於兩家分店的程式碼:

public class NYPizzaStore extends PizzaStore {
   @Override
   protected Pizza createPizza(String type) {
     if (type.equals("cheese")) {
       return new NYCheesePizza();
     }
     return null;
   }
}
 
public class ChicagoPizzaStore extends PizzaStore {
     @Override
     protected Pizza createPizza(String type) {
        if (type.equals("cheese")) {
       return new ChicagoCheesePizza();
     }
     return null;
   }
}    

 我們需要建立一個 Pizza 實體類:

public abstract class Pizza {
    String name; //名稱
    String dough; //麵糰型別
    String sauce; //醬料
    ArrayList<String> toppings = new ArrayList<String>(); //作料
 
    void prepare() {
        System.out.println("準備 " + name);
        System.out.println("揉麵團...");
        System.out.println("新增醬料...");
        System.out.println("新增作料: ");
        for (int i = 0; i < toppings.size(); i++) {
            System.out.println("   " + toppings.get(i));
        }
    }
    void bake() {
        System.out.println("烘烤25分鐘");
    }
    void cut() {
        System.out.println("把Pizza對角切片");
    }
    void box() {
        System.out.println("把Pizza裝盒子");
    }
    public String getName() {
        return name;
    }
}

然後需要一些具體的子類,下邊定義兩個子類:紐約風味的芝士披薩(NYStyleCheesePizza)、芝加哥風味的芝士披薩 (ChicageStyleCheesePizza)

public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza() { 
        name = "NY Style Sauce and Cheese Pizza";
        dough = "Thin Crust Dough";
        sauce = "Marinara Sauce";
        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";
        toppings.add("Shredded Mozzarella Cheese");
    }
    //可以覆蓋cut()方法
    void cut() {
        System.out.println("Cutting the pizza into square slices");
    }
}

2.2  總結

所有工廠模式都用來封裝物件建立,工廠方法模式通過讓子類決定該建立的物件是什麼,來達到將物件建立的過程封裝的目的。

工廠方法模式定義了一個建立物件的介面,但由子類決定要例項化的類是哪一個,工廠方法讓類把例項化推遲到子類。

建立者(Creator)類

 

產品類

 

簡單工廠和工廠方法之間的差異?

簡單工廠是在一個地方把所有的事都處理完了,然而工廠方法卻是建立一個框架,讓子類決定要如何實現。簡單工廠的做法,可以將物件的建立封裝起來,但是簡單工廠不具備工廠方法的彈性,因為簡單工廠不能變更正在建立的產品。

抽象工廠

建立工廠介面

回到上文的 Pizza 店,現在有新的需求,想要確保每家加盟店使用高質量的材料,打算建立一家生產原料的加工廠,並將原料送到各個加盟店。這個工廠負責建立原料家族中的每一種原料,工廠需要生產麵糰、醬料、芝士等。先為工廠定義一個介面,該介面負責所有原料:

public interface PizzaIngredientFactory {
        Dough CreateDough();
 
        Sauce CreateSauce();
 
        Cheese CreateCheese();
 
        Veggies[] CreateVeggies();
 
        Pepperoni CreatePepperoni();
 
        Clams CreateClam();
}

建立一個原料廠

//具體原料工廠必須實現這個介面
public class NYPizzaIngredientFactory extends PizzaIngredientFactory  {
        public Dough CreateDough() {
            return new ThinCrustDough();
        }
 
        public Sauce CreateSauce() {
            return new MarinaraSauce();
        }
 
        public Cheese CreateCheese() {
            return new ReggianoCheese();
        }
 
        public Veggies[] CreateVeggies() {
            Veggies[] veggies = new Veggies[]{ new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
            return veggies;
        }
 
        public Pepperoni CreatePepperoni() {
            return new SlicedPepperoni();
        }
 
        public Clams CreateClam() {
            return new FreshClams();
        }
}

重新抽象 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 ArrayList toppings = newArrayList();
 
       public abstract void Prepare();//把Prepare()方法宣告成抽象,在這個方法中,我們需要收集Pizza所需的原材料,而這些原材料來自原料工廠。
public void Bake() { System.Console.WriteLine("Bakefor 25 minutes at 350"); } public void Cut() { System.Console.WriteLine("Cuttingthe pizza into diagonal slices"); } public void Box() { System.Console.WriteLine("Placepizza in official PizzaStore box"); } public string GetName() { return name; } }

重新實現 Pizza

public class NYStyleCheesePizza extends Pizza {
        PizzaIngredientFactory ingredientFactory;
public NYStyleCheesePizza(PizzaIngredientFactoryingredientFactory){
      //製作Pizza需要工廠提供原材料,所以每個pizza類都需要從構造器中得到一個工廠,並將工廠儲存在變數中 this.ingredientFactory =ingredientFactory; name = "NY StyleSauc and Cheese Pizza"; toppings.Add("GratedReggiano Cheese"); } public override void Prepare() { System.Console.WriteLine("Preparing" + name); dough = ingredientFactory.CreateDough(); sauce = ingredientFactory.CreateSauce(); cheese = ingredientFactory.CreateCheese(); } }

重新生產pizza

public class MYPizzaStore extends PizzaStore {
        public  override Pizza CreatePizza(string type) {
            Pizza pizza=null;
            PizzaIngredientFactory ingredientFactory= new NYPizzaIngredientFactory();
            switch(type) {
                case "cheese":
                    pizza = new NYStyleCheesePizza(ingredientFactory);
                    break;
                case "veggie":
                    pizza=new NYStyleVeggiePizza(ingredientFactory);
                    break;
                case "clam":
                    pizza=new NYStyleClamPizza(ingredientFactory);
                    break;
                case "pepperoni":
                    pizza=new NYStylePepperoniPizza(ingredientFactory);
                    break;
            }
            return pizza;
        }
    }

通過這一系列的操作,我們引入了新型別的工廠,也就是所謂的“抽象工廠”,來建立 pizza 原來家族。通過抽象工廠所提供的介面建立產品家族,利用這個介面書寫程式碼,我們的程式碼將從實際工廠解耦,以便在不同上下文中實現各式各樣的工廠,製造出各種不同的產品。

定義抽象工廠模式

抽象工廠模式提供一個介面,用於建立相關或依賴物件家族,而且不需要致命具體類。

抽象工廠模式允許客戶使用抽象的介面來建立一組相關的產品,而不需要知道實際產出的具體產品是什麼,客戶就從具體的產品中被解耦。

抽象工廠與工廠方法的對比

抽象工廠和工廠方法都是負責建立物件。

抽象工廠是通過物件的組合

定義了一個建立物件的介面,但由於子類決定要例項化的類是哪一個,工廠方法讓類把例項化推遲到子類。

所以利用工廠方法建立物件時,需要擴充套件一個類,並覆蓋它的工廠方法。

整個工廠方法模式,只不過就是通過子類來建立物件,這種做法,客戶只需要知道他們所使用的抽象型別就可以了,而由子類負責決定具體型別。將客戶從具體型別中解耦。

工廠方法是繼承。

抽象工廠方法是將一群相關的產品集合起來,用於建立相關或依賴物件的家族,而不需要明確指定具體類。

 

 參考文章

https://blog.csdn.net/xuemoyao/article/details/53437609

https://www.cnblogs.com/lzhp/p/3375041.