1. 程式人生 > >Head First 設計模式之工廠模式(Factory Pattern)

Head First 設計模式之工廠模式(Factory Pattern)

前言:

     除了使用new操作符之外,還有更多製造物件的方法。你將瞭解到例項化這個活動不應該總是公開的進行,也會意識到初始化會造成“耦合”的問題。工廠模式將會從複雜的依賴中幫你脫困。

1.   簡單的工廠

當看到“new”,就會想到“具體”,的確也是在例項化一個具體的類,而不是介面。程式碼綁著具體的類導致程式碼更脆弱,更缺乏彈性。當有一群相關的具體類時,通常會有如下程式碼:

Duck duck;
If(picnic)
 duck=new MallardDuck();
else if(hunting)
 duck=new DecoyDuck();
else if(inBathTub)
 duck=new RubberDuck();
這樣的程式碼一旦有變化或者擴充套件,就必須重新修改此段程式碼進行檢查和修改,這樣的修改容易導致系統的維護和更新更難,也更容易犯錯。

針對介面程式設計,可以隔離掉以後系統可能發生的一大堆改變,因為針對介面而寫,可以通過多型,與任何新類實現該介面。當代碼使用具體類時,一旦加入新的一些具體類就必須改變程式碼。這就違背了“對修改關閉”的原則。為了解決這樣的問題,我們可以通過“找出變化,隔離並封裝變化“的方式來解決。

現實場景:

披薩店生產披薩,當需要生產更多型別的披薩的時候,壓力來自於如何增加更多的披薩型別。

public class Pizza
    {
        Pizza OrderPizza(stringpizzaType)
        {
            Pizza pizza;
            if (pizzaType.Equals("cheese"))
                pizza = newCheesePizza();
            else if(pizzaType.Equals("greek"))
                pizza = newGreekPizza();
            else if(pizzaType.Equals("pepperoni"))
                pizza = newPepperoniPizza();
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            return pizza;
        }
    }

如同開始是講的那樣,要新增新的pizza,就需要修改這段程式碼,修改如下:

public class Pizza
    {
        Pizza OrderPizza(stringpizzaType)
        {
            Pizza pizza;
            if(pizzaType.Equals("cheese"))
                pizza = newCheesePizza();
            else if(pizzaType.Equals("greek"))
                pizza = newGreekPizza();
            else if(pizzaType.Equals("pepperoni"))
                pizza = newPepperoniPizza();
            else if(pizzaType.Equals("clam"))  //新增的pizza型別
                pizza = newCalmPizza();
            else if(pizzaType.Equals("veggie"))//新增的pizza型別
                pizza = newVeggiePizza();
 
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            return pizza;
        }
    }

根據我們上邊提到的“將變化抽離並封裝“的原則,我們可以將建立pizza例項這一塊給抽離出來,因為這塊後邊可能會新增一些別的pizza型別,由一個物件來專職建立pizza物件。我們稱這個新物件為”工廠“。程式碼如下:
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物件的方法包裝成一個類,當以後實現改變時,只需要修改這個類即可。與此同時我們還可以把生成其他類的方法也放在這個簡單的工廠中。

這樣我們生成pizza類的程式碼就變成如下的樣子:

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

經過這樣一系列的修改,我們的Pizza的類圖就變成如下的樣子:


雖然我們一直在說簡單的工廠,但事實上簡單工廠並不是一個設計模式,更像是一種程式設計習慣。這裡講簡單的工廠,主要是為了引出下面的兩個重量級的模式,它們都是工廠。

2.   工廠方法

在1中,我們通過簡單的工廠解決了生產不同披薩的問題,但是,如果有新的加盟店加盟進來,如何解決不同加盟店的區域差異、質量問題呢?或許,我們可以像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 PizzaCreatePizza(string pizzaType);//把工廠物件移到該方法中,該方法為抽象方法
   }

現在我們有了PizzaStore超類,讓各個不同地域的加盟店繼承此超類即可。具體的類圖如下:


2.1  宣告一個工廠方法

原本是由一個物件複製所有具體類的例項化,現在通過對PizzaStore做一些轉變,變成由一群子類負責例項化。

工廠方法用來處理物件的建立,並將這樣的行為封裝在子類中。這樣客戶程式中關於超類的程式碼就和子類物件建立程式碼解耦。

2.2 具體程式碼實現

2.2.1 定義抽象的PizzaStore類,並抽象出工廠方法
public abstract class PizzaStore
    {
       public Pizza OrderPizza(stringtype)
       {
           Pizza pizza = CreatePizza(type);
           pizza.Prepare();
           pizza.Bake();
           pizza.Cut();
           pizza.Box();
 
           return pizza;
       }
      public abstract Pizza CreatePizza(stringtype);//抽象出建立Pizza的工廠方法,由子類實現該方法並建立具體的Pizza
    }
2.2.2 實現具體的PizzaStore類,讓子類做決定
public class MYPizzaStore:PizzaStore
    {
        public  override Pizza CreatePizza(string type)
        {
            Pizza pizza=null;
            switch(type)
            {
                case "cheese":
                    pizza = new NYStyleCheesePizza();
                    break;
                case "veggie":
                    pizza=new NYStyleVeggiePizza();
                    break;
                case "clam":
                    pizza=new NYStyleClamPizza();
                    break;
                case "pepperoni":
                    pizza=new NYStylePepperoniPizza();
                    break;
            }
            return pizza;
        }
    }
2.2.3抽象Pizza類,並實現具體的Pizza類
2.2.3.1 抽象Pizza類
public abstract  class Pizza
    {
       public string name;
       public string dough;
       public string sauce;     
       public ArrayList toppings = newArrayList();
       public void Prepare()
       {
            System.Console.WriteLine("Preparing" + name);
            System.Console.WriteLine("Tossingdough...");
            System.Console.WriteLine("Addingsauce..");
            System.Console.WriteLine("Addingtoppings: ");
            for(int i = 0; i < toppings.Count; i++)
            {
                System.Console.WriteLine(" "+ toppings[i]);
            }
        }
 
        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;
        }
}
2.2.3.2 具體的Pizza類
public class NYStyleCheesePizza : Pizza
    {
        public NYStyleCheesePizza()
        {
            name = "NY StyleSauc and Cheese Pizza";  
            dough="Thin Crust Dough";      
            sauce="Marinara Sauce";
            toppings.Add("GratedReggiano Cheese");
        }
    }

2.2  總結

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

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

建立者(Creator)類


產品類


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

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

3.   依賴倒置原則

要依賴抽象,不要依賴具體類

不能讓高層元件依賴底層元件,而且不管高層、底層元件,兩者都應該依賴於抽象。

如何避免違反依賴倒置原則:

變數不可以持有具體類的引用。

如果使用new,則會持有具體類的引用,可以用工程來避開這樣的做法

不要讓類派生自具體類。

如果派生自具體類,你就會依賴具體類(請派生自一個抽象類或介面)

不要覆蓋基類中已實現的方法。

如果覆蓋基類已實現的方法,那麼你的基類就不是一個真正適合被繼承的抽象。基類中已實現的方法,應該由所有的子類共享。

4.   抽象工廠

4.1 建立工廠介面

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

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

3.2建立原料工廠

public class NYPizzaIngredientFactory : 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();
        }
}

4.3 重新抽象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;
        }
    }

4.4 重新實現Pizza

public class NYStyleCheesePizza : 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();
        }
   
    }

4.5 重新生產pizza

public class MYPizzaStore: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原來家族。通過抽象工廠所提供的介面建立產品家族,利用這個介面書寫程式碼,我們的程式碼將從實際工廠解耦,以便在不同上下文中實現各式各樣的工廠,製造出各種不同的產品。

4.6 定義抽象工廠模式

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

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

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

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

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

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

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

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

工廠方法是繼承。

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