本期咱們要介紹一個能讓你烘烤本身的OO設計的一種模式,工廠模式
請問除了使用new之外,你還有其餘創造對象的方法嗎?若是你說沒有,那麼和我一塊兒好好學習下這個模式吧。你會認識到每每實例化不該該老是公開進行,也會認識到初始化常常形成的「耦合」問題。java
在以前的模式當中,我有介紹過一個原則,咱們不該該針對實現編程。但當看到「new」時,就會想到「具體」,不管是 A a=new A()仍是 A a=new B(),都其實在實例化一個具體類,因此的確用的是實現,而不是接口,或者再好比在以前「模擬鴨子」當中
Duck d = new MallardDuck(); Duck是一個接口,但MallardDuck仍是得創建具體類呀,OK若這樣想那麼至少明白了,代碼與具體類「捆綁」是耦合,缺乏彈性一種表現。數據庫
那這時就有小夥伴說了:「但總要有建立對象的時候把,Java中只提供了new關鍵字鍵對象,還能有別的嗎?」 固然new關鍵字自己沒有問題,這是Java原生部分,其實阻礙咱們的是「改變」,針對接口編程,能夠隔離掉當「改變」來了之後的許多問題,若是代碼是針對接口(這裏我在此強調,接口是一個大概念,不僅僅interface是接口,抽象類,類也能是一個「接口」)而寫,那麼經過多態,它能夠與任何新類實現該接口。可是我我的也以爲彷佛有問題:當代碼大量使用具體類時,會自找麻煩,由於當一旦要求增長某些內容時,就必須改變代碼。也就是說這樣代碼也不是「對修改關閉」(前期內容)了,想要新的具體類型來拓展代碼,必須從新修改並覆蓋它。編程
說了那麼多,具體咋寫代碼呢,不能紙上談兵把?OK,我具體來寫個實例。假設你有家披薩店。設計模式
注:設計模式須要反覆屢次練習,方可靈活運用,前期能夠先模仿。
一開始這樣寫(這裏我引用書中筆記)
app
固然咱們還要傳入類型來製做不一樣口味的披薩,以下圖
框架
可是你知道嗎?這樣設計是會有很大壓力的,好比你要添加新的口味披薩,或者取出掉某些賣的很差風味的披薩的話。要這麼寫,不管增長新披薩或者刪除掉原有披薩你都要到這裏操做,上面部分會進行修改,而下面部分準備原料,烘烤,切片,裝盤,操做都是不改變的,只有上面打算作什麼口味披薩的這個動做會改變。ide
注:請區別出哪些是會改變的,哪些不會改變
咱們作的改進,封裝建立對象的代碼,要把建立披薩的代碼轉移到另外一個對象中,由這個對象專職建立披薩,這個新對象只幹這一件事,如圖
學習
沒錯,咱們稱呼爲它「工廠」。
在JavaWeb當中有個持久層框架Hibernate,其一個核心接口「工廠」(SessionFactory)就是用到工廠模式,負責初始化hibernate,是數據源的代理,負責建立Session對象,一個數據庫對象只需配置一個SessionFactory,如有多個也能夠配置多個。測試
工廠(factory)處理建立對象的細節,一旦有了Factory,orderPizza()就變成了,此對象的客戶。當須要什麼類型披薩時,就叫披薩工廠作一個。如今orderPizza()方法只關心從工廠獲得一個披薩,而這個披薩實現了Pizza接口,因此能夠調用準備材料,烘烤,切片,裝盤方法。spa
那咱們來建立個簡單工廠。
可能有小夥伴提問:「感受只是把以前會被大量修改的問題移到另外一個對象裏而已」
注:SimlePizzaFactory會有不少客戶,目前咱們看到orderPizza()方法是它客戶,可是,可能還有PizzaShopMenu(披薩店菜單),xxxxPizzaShop(各類不一樣風格披薩店)等等會利用這個工廠來取得披薩里的內容,總而言之,SimlePizzaFactory會有許多客戶。因此,把建立披薩的代碼包裝進一個類,當之後實現改變時,只需修改此類便可。我正要把具體實例化的過程,在客戶代碼中刪除。
此時咱們寫一個披薩「工廠」的客戶類(PizzaStore)
以上就是簡單工廠模式的內容了,
注:簡單工程嚴格來講不是一個設計模式,更像是一種編程習慣,但因爲常常被使用,因此有時候不少開發人員誤認爲「工廠模式」。
下面是簡單工程的類圖。(請注意理解)
注:
再次提醒:所謂的「實現一個接口」並不必定表示「寫一個(interface)類,而後利用implements關鍵詞來實現某個Java接口」。「實現一個接口」泛指「實現某個超類型(能夠是類或接口)的某個方法」。
你覺得這篇文章寫道這裏就結束了?哈哈,別忘了個人口號是啥~~
既然咱們有了本身的披薩店,咱們固然但願將它作大作強,咱們開始加盟披薩店。
這裏咱們建立對應的「工廠」對象。
但這又有一個問題了,難道你能聽任加盟店無論嗎?這樣各個加盟店就按照本身的方式,烘烤的作法不一樣,不要切片,使用其餘廠商的盒子,若是可以創建一個框架,把加盟店和建立披薩捆綁在一塊兒的同時又保持必定的彈性。
有個作法能夠把披薩製做活動侷限在客戶(PizzaStore)類中,而同時又能讓這些加盟店依然能夠自由的建立該區域披薩,就是將createPizza方法放回PizzaStore內,將其設置爲「抽象方法」,讓後每一個區域的加盟店建立它的子類,就是說二者之間造成依賴關係。
讓咱們詳細一點。
從orderPizza()方法觀點來看,此方法在抽象父類PizzaStore裏定義,但具體實現仍是在不一樣子類中。面對每一個不一樣子類,父類的orderPizza()方法其實並不知道哪一個子類將實際上製做披薩。
orderPizza()方法對Pizza對象指向,(準備材料,烘烤,切片,裝盒)但因爲Pizza對象是抽象的,orderPizza()對象也並不知道哪些實際參與的具體類,換句話說,這就是解耦。(WTF?這就是解耦,不少時候每每初學設計模式的人還不能作到很敏感,包括我本身,須要長時間不斷學習)
OK,咱們這時候開一家加盟店咯。
注:超類(父類)PizzaStore中orderPizza()方法,並不知道正要建立什麼披薩,它只知道製做披薩有四個步驟。。。
如今咱們建立工廠方法,以前都是new,對象負責具體類的實例化,如今咱們經過PizzaStore作一個小轉變(其實在這裏,我我的感受說他是工廠方法感受怪怪的)
咱們再來鞏固下工廠方法(這裏我放出個人筆記截圖)
咱們來寫下購買披薩流程
1:首先,客戶們須要取得披薩店實例。客戶須要實例化一個NYStylePizzaStore或ChicagoStylePizzaStore
2:有了各自的PizzaStore訂單,客戶們分別調用orderPizza()方法,並傳入他們所喜好的比薩類型(芝士,素食)。
3:orderPizza調用createPizza()建立披薩,紐約風味披薩(NYStylePizzaStore)或者芝加哥風味披薩(ChicagoStylePizzaStore)。
4:orderPizza()並不知道具體建立的是哪種披薩,只知道這是一個披薩,可以準備原料,烘烤,切片,被打包裝盒,而後送出給顧客。
定義工廠方式模式:工廠模式定義了一個建立對象的接口,不在直接使用new了,但由子類決定要實例化的類是哪個。工廠方法讓類把實例化推遲到子類了。
注:工廠方法模式可以封裝具體類型的實例化。工廠方法讓子類決定要實例化的類是哪個。所謂「決定」,並非指模式容許子類自己在運行時作決定,而是指在編寫建立者類時,不須要知道實際建立的產品是哪個。選擇了哪一個子類,天然就決定了實際建立的東西是什麼(可能有點難以理解。。。)
下圖是工廠方法模式類圖
好的說這麼多,下面就是具體實現的時候了,因爲此例子量稍多,可是量大不表明我會偷工減料的,嘿嘿嘿~~~
這是參考的Java工程class圖
代碼開始
PizzaStore(工廠類)
package Store; import Pizza.Pizza; /** * PizzaStore至關於客戶(工廠類), 日後會有不少相似此類進行拓展 * * 在這裏orderPizza()方法對pizz對象作了不少事(createPizza,prepare,bake,cut,box) * 可是因爲Pizza對象是抽象的(接口), * orderPizza()並不知道具體哪些實際類參與進來了(至關於實現Pizza接口的實現類有哪些orderPizza()方法並不知道) * 換句話說,這就是解耦,減小相互依賴 * * 同理createPizza()方法中也不知道建立的具體是什麼披薩 只知道披薩能夠被準備,被烘烤,被切片,被裝盒行爲而已 * */ // 披薩店 public abstract class PizzaStore { // 點單 public Pizza orderPizza(String type) { // createPizza()方法從工廠對象中取出, // 而且方法也再也不這裏實現,定義爲抽象類,誰須要在拿走方法去具體實現 Pizza pizza = createPizza(type); System.out.println("----製做一個" + pizza); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } // 把工廠對象移到這個方法裏,並封裝 // 如今工廠方法爲抽象的,子類必須實現這個抽象方法 public abstract Pizza createPizza(String item); }
下面是「工廠」具體實現
紐約風格披薩店(分店)
package Store; import Pizza.NYStyleCheesePizza; import Pizza.NYStyleClamPizza; import Pizza.NYStylePepperoniPizza; import Pizza.NYStyleVeggiePizza; import Pizza.Pizza; /** * 此時NYStylePizzaStore所封裝的是關於如何製做紐約風格的比薩 * * @author Joy * */ // 紐約風格披薩店(分店) public class NYStylePizzaStore extends PizzaStore { // 根據披薩的不一樣,建立不一樣披薩(具體實現類) @Override public Pizza createPizza(String item) { if (item.equals("芝士")) { return new NYStyleCheesePizza(); } else if (item.equals("素食")) { return new NYStyleVeggiePizza(); } else if (item.equals("海鮮")) { return new NYStyleClamPizza(); } else if (item.equals("香腸")) { return new NYStylePepperoniPizza(); } return null; } }
芝加哥披薩店(分店)
package Store; import Pizza.ChicagoStyleCheesePizza; import Pizza.ChicagoStyleClamPizza; import Pizza.ChicagoStylePepperoniPizza; import Pizza.ChicagoStyleVeggiePizza; import Pizza.Pizza; /** * 此時ChicagoStylePizzaStore所封裝的是關於如何製做芝加哥風格的比薩 * * @author Joy * */ // 芝加哥披薩店(分店) public class ChicagoStylePizzaStore extends PizzaStore { @Override public Pizza createPizza(String item) { if (item.equals("芝士")) { return new ChicagoStyleCheesePizza(); } else if (item.equals("素食")) { return new ChicagoStyleVeggiePizza(); } else if (item.equals("海鮮")) { return new ChicagoStyleClamPizza(); } else if (item.equals("香腸")) { return new ChicagoStylePepperoniPizza(); } return null; } }
Pizza類
(至關於各個「產品」的接口)
package Pizza; import java.util.ArrayList; public abstract class Pizza { String name;// 披薩名稱 String dough;// 麪糰類型 String sauce;// 醬料 ArrayList toppings = new ArrayList();// 一套佐料 public 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.print(toppings.get(i) + " "); } } public void bake() { System.out.println("\n" + "披薩正在烘烤,需烘烤25分鐘。。。"); } public void cut() { System.out.println("製做完成,給披薩切片。。。"); } public void box() { System.out.println("給披薩打包裝盒。。。"); } public String getName() { return name; } // 輸出客人點的披薩信息 @Override public String toString() { StringBuffer display = new StringBuffer(); display.append(name + " ----\n"); display.append(dough + "\n"); display.append(sauce + "\n"); for (int i = 0; i < toppings.size(); i++) { display.append((String) toppings.get(i) + "\n"); } return display.toString(); } }
下面是各類披薩具體實現類
package Pizza; //加州披薩店的芝士披薩 public class ChicagoStyleCheesePizza extends Pizza { public ChicagoStyleCheesePizza() { name="加州芝士披薩"; dough="厚餅"; sauce="番茄醬"; toppings.add("幹碎奶酪"); } @Override public void cut() { System.out.println("將披薩切成方形"); } }
package Pizza; //加州披薩店的海鮮披薩 public class ChicagoStyleClamPizza extends Pizza { public ChicagoStyleClamPizza() { name = "加州海鮮披薩"; dough = "厚餅"; sauce = "番茄醬"; toppings.add("幹碎奶酪"); toppings.add("蟹黃"); toppings.add("龍蝦"); toppings.add("各類海鮮"); } @Override public void cut() { System.out.println("將披薩切成三角形"); } }
package Pizza; //加州披薩店裏的香腸披薩 public class ChicagoStylePepperoniPizza extends Pizza { public ChicagoStylePepperoniPizza() { name = "加州香腸披薩"; dough = "厚餅"; sauce = "番茄醬"; toppings.add("幹碎奶酪"); toppings.add("黑胡椒"); toppings.add("菠菜"); toppings.add("茄子"); toppings.add("香腸切片"); } @Override public void cut() { System.out.println("切成方形薄披薩"); } }
package Pizza; //加州披薩店裏的素披薩 public class ChicagoStyleVeggiePizza extends Pizza { public ChicagoStyleVeggiePizza() { name = "加州素披薩"; dough = "厚餅"; sauce = "番茄醬"; toppings.add("幹碎奶酪"); toppings.add("聖女果"); toppings.add("黑胡椒粉"); toppings.add("紅辣椒"); toppings.add("甜玉米粒"); toppings.add("色拉"); } @Override public void cut() { System.out.println("切成方形薄披薩"); } }
package Pizza; //紐約披薩店裏的芝士披薩 public class NYStyleCheesePizza extends Pizza { public NYStyleCheesePizza() { name="紐約芝士披薩"; dough="薄餅"; sauce="番茄醬"; toppings.add("幹奶酪"); } }
package Pizza; //紐約披薩店裏的河鮮披薩 public class NYStyleClamPizza extends Pizza { public NYStyleClamPizza() { name = "紐約海鮮披薩"; dough="薄餅"; sauce="番茄醬"; toppings.add("巴馬乾奶酪"); toppings.add("蟹黃"); toppings.add("龍蝦"); } }
package Pizza; //紐約披薩店裏的香腸披薩 public class NYStylePepperoniPizza extends Pizza { public NYStylePepperoniPizza() { name = "紐約香腸披薩"; dough="薄餅"; sauce="番茄醬"; toppings.add("巴馬乾酪奶酪"); toppings.add("意大利香腸切片"); toppings.add("大蒜"); toppings.add("洋蔥"); toppings.add("香菇"); toppings.add("紅辣椒"); } }
package Pizza; //紐約披薩店裏的素披薩 public class NYStyleVeggiePizza extends Pizza { public NYStyleVeggiePizza() { name = "紐約素食披薩"; dough = "薄餅"; sauce = "番茄醬"; toppings.add("巴馬乾酪奶酪"); toppings.add("洋蔥"); toppings.add("各類蔬菜"); } }
測試類
package TestMain; import Pizza.Pizza; import Store.ChicagoStylePizzaStore; import Store.NYStylePizzaStore; import Store.PizzaStore; public class TestMain { public static void main(String[] args) { PizzaStore nyStore = new NYStylePizzaStore(); PizzaStore chicagoStore = new ChicagoStylePizzaStore(); // System.out.println("===================================="); // Pizza pizza1 = nyStore.orderPizza("芝士"); // System.out.println("Joy點了一個" + pizza1.getName()); // System.out.println("===================================="); // Pizza pizza2 = nyStore.orderPizza("素食"); // System.out.println("Joy點了一個" + pizza2.getName()); System.out.println("===================================="); Pizza pizza3 = nyStore.orderPizza("海鮮"); System.out.println("Joy點了一個" + pizza3.getName() + " ,正在送出"); // System.out.println("===================================="); // Pizza pizza4 = nyStore.orderPizza("香腸"); // System.out.println("Joy點了一個" + pizza4.getName()); System.out.println("===================================="); Pizza pizza11 = chicagoStore.orderPizza("芝士"); System.out.println("Joy點了一個" + pizza11.getName() + " ,正在送出"); // System.out.println("===================================="); // Pizza pizza22 = chicagoStore.orderPizza("素食"); // System.out.println("Joy點了一個" + pizza22.getName()); // System.out.println("===================================="); // Pizza pizza33 = chicagoStore.orderPizza("海鮮"); // System.out.println("Joy點了一個" + pizza33.getName()); // System.out.println("===================================="); // Pizza pizza44 = chicagoStore.orderPizza("香腸"); // System.out.println("Joy點了一個" + pizza44.getName()); } }
效果以下:
這個模式是個頗有用的模式,讓我更加能封裝變化了,模擬代碼已經所有放出,註釋也寫的比較清楚,如有不理解歡迎留言。
感謝你看到這裏,工廠模式上部分結束,但工廠模式內容還沒徹底結束 本人文筆隨便,如有不足或錯誤之處望給予指點,90度彎腰~~~很快我會補全完這個內容。 生命不息,編程不止!
參考書籍:《Head First 設計模式》