前言:按照慣例我以Head First設(shè)計(jì)模式的工廠模式例子開始編碼學(xué)習(xí)。并由簡單工廠,工廠模式,抽象工廠模式依次演變,歸納他們的相同與不同。
話說Head First認(rèn)為簡單工廠并不是設(shè)計(jì)模式,而是一種編程習(xí)慣,但并不妨礙我們使用它,接下來我們對工廠模式一探究竟。
1、披薩店例子
首先我們要開一個披薩店,對于業(yè)務(wù)不復(fù)雜的情況下我們可以快速的開發(fā)出一個披薩店以及訂購披薩的邏輯
public Pizza OrderPizza() { Pizza pizza = new Pizza(); pizza.Prepare();
pizza.Bake(); pizza.Cut(); pizza.Box(); return pizza; } } public class Pizza {
//準(zhǔn)備 public void Prepare() { } //烘烤 public void Bake() { } //切片 public void
Cut() { } //裝盒 public void Box() { } }
? ? ?如果我們有更多的披薩種類可能需要將Pizza定義成抽象類 在訂單里面根據(jù)訂購的披薩種類返回不同的披薩,我們對披薩進(jìn)行抽象并改造Order。
public class PizzaStore { public Pizza OrderPizza(string type) { Pizza
pizza=null; if (type == "cheese") { pizza = new CheesePizza(); } else if (type
== "viggie") { pizza = new VeggiePizza(); } //else if ...... pizza.Prepare();
pizza.Bake(); pizza.Cut(); pizza.Box(); return pizza; } } public abstract class
Pizza { //準(zhǔn)備 public void Prepare() { } //烘烤 public void Bake() { } //切片 public
void Cut() { } //裝盒 public void Box() { } } //奶酪披薩 public class CheesePizza :
Pizza { } //素食披薩 public class VeggiePizza : Pizza { }
? ?到這里我們可能想到了,如果增加披薩種類或者移除披薩那么我們將對披薩店進(jìn)行修改。
? ?設(shè)計(jì)原則對擴(kuò)展開放,對修改關(guān)閉。我們需要將創(chuàng)建披薩的變化封裝起來。對此弄出來一個專門創(chuàng)建披薩的“工廠“類。
? ?并采用靜態(tài),這樣就不需要實(shí)例化對象,也遵循了不對實(shí)現(xiàn)編程原則。
public class PizzaStore { public Pizza OrderPizza(string type) { Pizza pizza
= SimplePizzaFactory.CreatePizza(type); pizza.Prepare(); pizza.Bake();
pizza.Cut(); pizza.Box(); return pizza; } } public static class
SimplePizzaFactory { public static Pizza CreatePizza(string type) { Pizza pizza
= null; if (type == "cheese") { pizza = new CheesePizza(); } else if (type ==
"viggie") { pizza = new VeggiePizza(); } return pizza; } }
這樣將創(chuàng)建披薩簡單的封裝起來即是簡單工廠(靜態(tài)工廠),簡單工廠也可以不用靜態(tài)類,但簡單工廠并不是一種專門的設(shè)計(jì)模式(有時候可能會混淆,認(rèn)為這即是”工廠模式“),更像是我們平時編程都會做的一種習(xí)慣。我們將改動封裝在一個局部當(dāng)有變化的時候只需要修改這個工廠類。
2、更多的披薩店
現(xiàn)在我們要開更多的披薩店,例如美國風(fēng)格披薩店(USSytlePizzaStore)、中國風(fēng)格披薩店(CHNSytlePizzaStore)。
我們可以采用簡單工廠模式,創(chuàng)建兩個不同風(fēng)格的披薩工廠,然后創(chuàng)建兩個不同風(fēng)格的披薩店,不同風(fēng)格的披薩店使用對應(yīng)的披薩工廠來獲取。
但是我們此時的變化點(diǎn)是披薩店。我們希望披薩店的結(jié)構(gòu)或者流程是按照一定規(guī)則的,只是不同風(fēng)格的披薩。此時我們有更好的解決辦法:工廠模式。
接下來我們看如何實(shí)現(xiàn)
public abstract class PizzaStore { public Pizza OrderPizza(string type) {
Pizza pizza= CreatePizza(type); pizza.Prepare(); pizza.Bake(); pizza.Cut();
pizza.Box(); return pizza; } public abstract Pizza CreatePizza(string type); }
public class USSytlePizzaStore : PizzaStore { public override Pizza
CreatePizza(string type) { Pizza pizza = null; if (type == "cheese") { pizza =
new USStyleCheesePizza(); } else if (type == "viggie") { pizza = new
USStyleVeggiePizza(); } return pizza; } } public class CHNSytlePizzaStore :
PizzaStore { public override Pizza CreatePizza(string type) { Pizza pizza =
null; if (type == "cheese") { pizza = new CHNStyleCheesePizza(); } else if
(type == "viggie") { pizza = new CHNStyleVeggiePizza(); } return pizza; } }
//US奶酪披薩 public class USStyleCheesePizza : Pizza { } //US素食披薩 public class
USStyleVeggiePizza : Pizza { } //CHN奶酪披薩 public class CHNStyleCheesePizza :
Pizza { } //CHN素食披薩 public class CHNStyleVeggiePizza : Pizza { }
? ?由實(shí)現(xiàn)我們可以看到我們將PizzaStore修改成抽象類,不同的披薩店繼承抽象類返回自己不同風(fēng)格的披薩。
? ?這樣設(shè)計(jì)后當(dāng)增加產(chǎn)品,我們也只是在具體的子類披薩店中修改其中的披薩創(chuàng)建,不會影響披薩店本身流程和其他披薩店的實(shí)現(xiàn)。
? 工廠方法模式:定義了一個創(chuàng)建對象的接口,由子類決定要實(shí)例化的類是哪一個,工廠方法讓類把實(shí)例化推遲到子類。
? 工廠方法與簡單工廠的區(qū)別:工廠方法的子類看起來很像簡單工廠。簡單工廠把全部的事情在一個地方處理完成,而工廠方法卻是創(chuàng)建一個框架,讓子類決定如何實(shí)現(xiàn)。
3、披薩的不同原料
不同風(fēng)格的披薩店有不同風(fēng)格的披薩,而這些披薩的不同風(fēng)格是來自不同原料造成,所以不同風(fēng)格的披薩變化的部分是材料。
我們先建造原料和原料工廠,以中國披薩原料工廠為例
//披薩原料工廠接口 public interface PizzaIngredientFactory { public Veggie
CreateVeggie(); public Cheese CreateCheese(); } //具體工廠實(shí)現(xiàn) public class
CNHPizzaIngredientFactory : PizzaIngredientFactory { public Cheese
CreateCheese() { return new CHNCheese(); } public Veggie CreateVeggie() {
return new CHNVeggie(); } } public abstract class Veggie { } public class
USVeggie : Veggie { } public class CHNVeggie : Veggie { } public abstract class
Cheese { } public class USCheese : Cheese { } public class CHNCheese : Cheese {
}
然后重做Pizza
public abstract class Pizza { public String Name; Veggie veggie; Cheese
cheese; //準(zhǔn)備 public abstract void Prepare(); //烘烤 public void Bake() { } //切片
public void Cut() { } //裝盒 public void Box() { } }
加入了原料的抽象 Veggie 和
Cheese,同時我們讓Prepare變成抽象方法,讓他的具體子類決定用什么材制造不同風(fēng)格的披薩。接著我們重做子類,以CheesePizza為例?
//奶酪披薩 public class CheesePizza : Pizza { PizzaIngredientFactory
IngredientFactory; public CheesePizza(PizzaIngredientFactory IngredientFactory)
{ this.IngredientFactory = IngredientFactory; } public override void Prepare()
{ IngredientFactory.CreateCheese(); } }
修改中國披薩店
public class CHNSytlePizzaStore : PizzaStore { public override Pizza
CreatePizza(string type) { Pizza pizza = null; //創(chuàng)建中國原材料工廠
CNHPizzaIngredientFactory ingredientFactory = new CNHPizzaIngredientFactory();
if (type == "cheese") { pizza = new CheesePizza(ingredientFactory); } else if
(type == "viggie") { pizza = new VeggiePizza(ingredientFactory); } return
pizza; } }
通過這一系列的改造我們引入了新類型的工廠,也就是所謂的抽象工廠,抽象工廠用來創(chuàng)造原料。
利用抽象工廠我們代碼將從實(shí)際工廠解耦,這樣如果我們的工廠需要擴(kuò)展那么我們則可在子類中進(jìn)行修改擴(kuò)展。
工廠方法與抽象工廠的異同優(yōu)缺點(diǎn)
相同:都是用來創(chuàng)建對象。
不同:工廠方法使用的是繼承,抽象工廠使用的是組合。
優(yōu)點(diǎn):工廠方法只負(fù)責(zé)從具體類型中解耦,抽象工廠適合將一群相關(guān)的產(chǎn)品集合起來。
缺點(diǎn):抽象工廠擴(kuò)展接口需要修改每個子類。