Head First 設計模式(4)----- 工廠方法模式

本文參照《Head First 設計模式》,轉載請註明出處 對於整個系列,咱們按照這本書的設計邏輯,使用情景分析的方式來描述,而且穿插使用一些問題,總結的方式來說述。而且全部的開發源碼,都會託管到github上。 項目地址:github.com/jixiang5200… git

回顧上一篇文章講解了設計模式中經常使用的一種模式------裝飾者模式。並結合星巴茲咖啡設計進行實戰解析,而且從本身設計到JAVA自帶設計模式作了講解。想要了解的朋友能夠回去回看一下。程序員

本章將着重於在開發中最常遇到的初始化的問題,讓你不會在new一個新的對象中感受頭疼。雖然本章取名是將工廠模式,可是這裏會講解兩個設計模式,分別爲工廠模式和抽象工廠設計模式。本章對於JAVA中抽象會大量運用到,未了解這方面的知識的朋友能夠去查閱相關的資料。github

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有一系列的實現類。可是具體使用哪個類,卻還須要經過屬性條件來決定。 到這裏,咱們就能夠提出問題:若是這部分屬性發生很大的變化,甚至被取代掉了,是否咱們對於這樣受到影響的代碼不都要作相關的操做?那這不就是違背了前面提到的一個概念解耦性。這段代碼的耦合度就很是高。bash

那麼問題來了:使用「new」到底有什麼不對勁?(new大法簡單粗暴啊,小姐姐最愛啊)架構

回答:前面提到一個概念「設計應該對拓展開放,對修改關閉」,new大法自己沒有錯誤,畢竟JAVA最基礎的組成架構。問題就是,這裏的方式沒作到拓展,也沒有作到對修改閉環。ide

在開始說優化的方式以前,咱們先來認識一下拓展和修改。 到這裏,Duck將退出舞臺 函數

image
接下來,將由PIZZA上臺
image
愛吃的吃貨都不會陌生,生產一份披薩須要通過如下幾個步驟:

  • 準備(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.簡單工廠(非設計模式)

簡單工廠其實嚴格意義上不是設計模式,而是一種編程習慣,仍是最經常使用的那種。。。。(哎,你別動手)

image
(屈服於大佬的淫威)可是呢,並不能說它不是一種設計模式就不重視它,從前面的PIZZA類圖分析:

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.工廠方法模式

從前面咱們認識了工廠方法,那麼到這裏就能夠推出咱們第一個工廠模式------工廠方法模式。

工廠方法模式定義了一個建立對象的接口,可是由子類決定要實例化的類是哪個。工廠方法讓類把實例化推遲到子類。

抽象結構以下:

工廠模式抽象結構
相關文章
相關標籤/搜索