本文參照《Head First 設計模式》,轉載請註明出處
對於整個系列,我們按照這本書的設計邏輯,使用情景分析的方式來描述,並且穿插使用一些問題,總結的方式來講述。並且所有的開發源碼,都會託管到github上。
項目地址:https://github.com/jixiang52002/HFDesignPattern
回顧上一篇文章講解了設計模式中常用的一種模式------裝飾者模式。並結合星巴茲咖啡設計進行實戰解析,並且從自己設計到JAVA自帶設計模式做了講解。想要了解的朋友可以回去回看一下。
本章將着重於在開發中最常遇到的初始化的問題,讓你不會在new一個新的對象中感覺頭疼。雖然本章取名是將工廠模式,但是這裏會講解兩個設計模式,分別爲工廠模式和抽象工廠設計模式。本章對於JAVA中抽象會大量運用到,未了解這方面的知識的朋友可以去查閱相關的資料。
1.前言
作爲一個合格的JAVA開發程序員,我們知道要實例化一個類爲對象,我們會利用類中的構造函數,使用new這個關鍵字。比如:
Duck duck=new MallardDuck();
這裏使用接口的方式爲的使代碼具備彈性,但是如果我們需要根據屬性值去賦值,比如下面這樣的表達
Duck duck;
if(picnic) {
duck=new MallardDuck();
}else if (hunting) {
duck=new DecoyDuck();
}else if (inBathTub) {
duck=new RubberDuck();
}
這裏對於Duck有一系列的實現類。但是具體使用哪一個類,卻還需要通過屬性條件來決定。
到這裏,我們就可以提出問題:如果這部分屬性發生很大的變化,甚至被取代掉了,是否我們對於這樣受到影響的代碼不都要做相關的操作?那這不就是違背了前面提到的一個概念解耦性。這段代碼的耦合度就非常高。
那麼問題來了:使用“new”到底有什麼不對勁?(new大法簡單粗暴啊,小姐姐最愛啊)
回答:前面提到一個概念“設計應該對拓展開放,對修改關閉”,new大法本身沒有錯誤,畢竟JAVA最基礎的組成架構。問題就是,這裏的方式沒做到拓展,也沒有做到對修改閉環。
在開始說優化的方式之前,我們先來認識一下拓展和修改。
到這裏,Duck將退出舞臺
接下來,將由PIZZA上臺
愛喫的喫貨都不會陌生,生產一份披薩需要經過以下幾個步驟:
- 準備(prepare)
- 烤制 (bake)
- 切片 (cut)
- 裝盤 (box)
用代碼來表示應該是如下步驟:
public Pizza orderPizza() {
Pizza pizza=new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
但是我們知道PIZZA有很多種類,比如榴蓮餡,奧爾良餡,芝士餡等,那爲了能夠適應這個需求,我們對於源代碼需要更改:
public Pizza orderPizza(String type) {
Pizza pizza = null;
if(type.equals("cheese")) {
pizza=new CheesePizza();
}else if (type.equals("greek")) {
pizza=new GreekPizza();
}else if (type.equals("pepperoni")) {
pizza=new PepperoniPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
到這裏就是熟悉的配方,熟悉的new大法,如果只有兩三個類型都還好,但是如果有上萬個類型選項呢,甚至可能由於CheesePizza賣的不是很好,我們需要將其去掉呢?是不是想要拓展或者修改,對於原有業務的修改會非常驚人。這時候,封裝上馬了。
2.封裝
從前面分析,我們清楚需要將創建的代碼從原有的業務中抽離出來。
首先,分析一下,其中準備到切片的過程是不變的(固定模塊),是類型type不同會導致結果不同(拓展/修改模塊)。所以我們將type初始化部分提取出來單獨作爲一個模塊。
public class SimplePizzaFactory {
/**
* 所有的客戶使用該方法來實例化對象
* @param type 類型
* @return PIZZA
*/
public Pizza createPizza(String type) {
Pizza pizza=null;
if(type.equals("cheese")) {
pizza=new CheesePizza();
}else if (type.equals("greek")) {
pizza=new GreekPizza();
}else if (type.equals("pepperoni")) {
pizza=new PepperoniPizza();
}
return pizza;
}
}
這個用以實例化對象的類,我們稱之爲“工廠(factory)”
question:這不就是把問題從一個地方移動到另一個地方了嗎,問題依然存在。
answer:雖然是移動了,但是SimplePizzaFactory 可不僅僅只能爲oderPizza提供對象初始化服務,還可以爲其他對象服務。這裏做一個比喻:一個業務部門(阿里支付部門)從公司分出成爲分公司(螞蟻金服),它既可以爲原來的業務服務(淘寶,天貓),也可以爲新的業務服務(移動支付,企業服務)。
question:將工廠方法定義爲靜態方法有什麼好處和壞處?
answer:利用靜態方法定義一個簡單的工廠模式,最爲常見(不一定是Factory類),常稱爲靜態工廠。
好處:不需要實例化對象,不佔據多少內存
壞處:無法通過繼承來改變內部的實現方法
按照簡單工廠我們可以修改自己的代碼
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory=factory;
}
/**
* 根據類型生成PIZZA
* @param type
* @return PIZZA
*/
public Pizza orderPizza(String type) {
Pizza pizza;
//orderPizza通過傳入type類型,使用工廠完成創建
pizza=factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
3.簡單工廠(非設計模式)
簡單工廠其實嚴格意義上不是設計模式,而是一種編程習慣,還是最常用的那種。。。。(哎,你別動手)
(屈服於大佬的淫威)但是呢,並不能說它不是一種設計模式就不重視它,從前面的PIZZA類圖分析:
其實這時候,我們已經接近了設計模式。所以接下來,就是最重要的設計模式。
4.新業務需求:加盟店模式
由於經營合理加上口味獨特,PIZZA店終於大獲成功。現在所有的消費者和餐飲投資人都希望在自己的附近能夠有一家加盟店。作爲經營者,你可以按照以下兩種模式來選擇加盟方式:
- 所有加盟店使用與總店一起使用相同的配方(直營店模式)
- 每家加盟店可以根據當地風味的不同,自己更改其中的配方和風味(加盟商模式)
從現在做的比較大的必勝客,肯德基,麥當勞的經營模式來看,後者會讓經營模式更加靈活。但是前者可以保證加盟店的產品質量和口碑,比如京東線下店,蘇寧易購等。所以我們來看看不同的加盟模式,會對原有的業務需求造成什麼影響。
4.1 完全直營店
首先是設計結構,PizzaStore
所以這裏我們需要拓展SimplePizzaFactory,這裏需要類似於NYPizzaFactory、ChicagoPizzaFactory、ChinaPizzaFactory(甚至可以再細分),那麼準備一份Pizza的流程就需要更改
NYPizzaFactory factory=new NYPizzaFactory();
PizzaStore nyStore=new PizzaStore();
nyStore.oderPizza("Veggie");
相對來說就非常簡單。那麼如果是加盟商模式呢
4.2 加盟商模式
在某些加盟商裏有一些經驗很豐富的廚師,他們在做Pizza的時候,會加入自己的一些思路和想法,比如:
過量的芝士,本地風味的榴蓮,甚至可能存在雙拼的情況。但是我們知道,我們在一開始是將這些操作放在SimplePizzaFactory裏,這就使得代碼結構不具備活性。就是前面提到的,拓展和修改方面不滿足需求。
那麼,如何修改現有的結構可以滿足需求呢?
按照前面的經驗:儘量拓展,少修改。我們發現有區別的地方在於各地的PizzaStore不同,那麼是否可以在PizzaStore這裏做拓展呢。
這裏可以嘗試把createPizza放回PizzaStore類裏面,但是不需要具體方法體,使用抽象類和抽象方法。讓具體的PizzaStore去實現具體的createPizza方法。看一下修改後的效果:
public abstract class AbstactPizzaStore {
/**
* 根據類型生成PIZZA
* @param type
* @return PIZZA
*/
public Pizza orderPizza(String type) {
Pizza pizza;
//orderPizza通過傳入type類型,使用工廠完成創建
pizza=createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
/**
* 工廠對象功能移到這裏
* @param type
* @return PIZZA
*/
abstract Pizza createPizza(String type);
}
這裏有了超類AbstractPizzaStore,就可以實現NYPizzaStore、ChicagoPizzaStore、ChinaPizzaStore等。
這裏這些子類就具備決定權。
public class NYPizzaStore extends AbstactPizzaStore {
@Override
public Pizza createPizza(String type) {
Pizza pizza=null;
if(type.equals("cheese")) {
pizza=new NYStyleCheesePizza();
}else if (type.equals("greek")) {
pizza=new NYStyleGreekPizza();
}else if (type.equals("pepperoni")) {
pizza=new NYStylePepperoniPizza();
}
return pizza;
}
}
分析:爲何這樣做會更好,好處就在於,PizzaStore的oderPizza不需要關注createPizza的Pizza是怎麼來的。它只知道createPizza返回的Pizza可以做後續操作。這種對於其他業務有哪些類參與進來的方法,我們稱爲解耦。
而這個抽象方法我們稱爲工廠方法,就是實現了工廠類效果的抽象方法。
/* 工廠對象功能移到這裏
* @param type
* @return PIZZA
*/
abstract Pizza createPizza(String type);
工廠方法必須具備以下幾個條件:
- 工廠方法是抽象的,依賴子類來處理具體邏輯
- 工廠方法必須返回一個產品對象,通常定義在返回值(也可以使用回調)
- 工廠方法需要將修改部分從穩定部分中抽離出來。
- 工廠方法中必須有創建者類(穩定部分)和產品類(修改部分)
到這裏,就完成了加盟店的設計。
5.工廠方法模式
從前面我們認識了工廠方法,那麼到這裏就可以推出我們第一個工廠模式------工廠方法模式。
工廠方法模式定義了一個創建對象的接口,但是由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到子類。
抽象結構如下: