模式系列之工廠模式

   簡介:工廠模式和抽象工廠模式是在日常開發中使用非常廣泛的設計模式。主要用於實現將對象的實例化部分取出來,進而優化系統架構,增強系統的擴展性。本課程即將講解Java中的工廠模式和抽象工廠模式的應用。

1. 工廠模式概述

1.1   工廠模式概念

   實例化對象,用工廠方法替代new操作

   工廠模式包括工廠方法模式和抽象工廠模式

   抽象工廠模式是工廠方法模式的擴展

1.2 工廠模式的意圖

定義一個接口來創建對象,但是讓子類來決定哪些類需要被實例化。

工廠方法把實例化的工作推遲到子類中去實現。

1.3 什麼情況下適合工廠模式?

有一組類似的對象需要創建。

在編碼時不能預見需要創建哪種類的實例。

系統需要考慮擴展,不應依賴於產品實例如何被創建、組合和表達的細節。

1.4 工廠模式的動機

項目中的現狀:

在軟件系統中經常面臨着“對象”的創建工作,由於需求的變化,這個對象可能隨之也會發生變化,但它卻擁有比較穩定的接口。

爲此,我們需要提供一種封裝機制來隔離出這個易變對象的變化,從而保持系統中其他依賴該對象的對象不隨着需求變化而變化。

基於項目現狀將代碼進行如下設計:

①儘量鬆耦合,一個對象的依賴對象的變化與本身無關

②具體產品與客戶端剝離,責任分割

1.5 分類

工廠模式主要是爲創建對象提供過渡接口

以便將創建對象的具體過程品比隔離起來,達到提高靈活性的目的

· 工廠模式在《Java與模式》中分爲三類: 

00001. 簡單工廠模式(Simple Factory)

00002. 工廠方法模式(Factory Method)

00003. 抽象工廠模式(Abstract Factory)

· 這三種模式從上到下,逐步抽象,並且更具一般性

· GOF在《設計模式》一書中將工廠模式分爲兩類: 

00001. 工廠方法模式(Factory Method)

00002. 抽象工廠模式(Abstract Factory)

· 將簡單工廠模式(Simple Factory)看爲工廠方法模式的一種特例,兩者歸爲一類。

1.5.1 簡單工廠模式

· 簡單工廠模式又稱靜態工廠方法模式

· 從命名上就可以看出這個模式一定很簡單

· 它存在的目的很簡單:定義一個用於創建對象的接口

· 簡單工廠模式的組成: 

·工廠類角色:這是本模式的核心,含有一定的商業邏輯和判斷邏輯,在Java中它往往由一個具體類實現

·抽象產品角色:它一般是具體產品繼承的父類或者實現的接口。在Java中由接口或者抽象類實現

·具體產品角色:工廠類所創建的對象就是此角色的實例。在java中由一個具體類實現


1.5.2 工廠方法模式

· 工廠方法模式去掉了簡單工廠模式中工廠方法的靜態屬性,使得它可以被子類繼承。

· 這樣在簡單工廠模式裏集中在工廠方法上的壓力可以由工廠方法模式裏不同的工廠子類來分擔。

· 下面,看看它的組成 

·抽象工廠角色:這是工廠方式模式的核心,它與應用程序無關,是具體工廠角色必須實現的接口或者必須繼承的父類。Java中它由抽象類或者接口實現

·具體工廠角色:它含有和具體業務邏輯有關的代碼。由應用程序調用以創建對應的具體產品對象

·抽象產品角色:它是具體產品繼承的父類或者是實現的接口。 java 中一般有抽象類或者接口來實現。

·具體產品角色:具體工廠角色所創建的對象就是此角色的實例。 java 中由具體的類來實現。 


1.5.3 抽象工廠模式

    先來認識下什麼是產品族: 位於不同產品等級結構中,功能相關聯的產品組成的家族。還是讓我們用一個例子來形象地說明一下吧。

·圖中的 BmwCar 和 BenzCar 就是兩個產品樹(產品層次結構);而如圖所示的BenzSportsCar 和 BmwSportsCar 就是一個產品族。他們都可以放到跑車家族中,因此功能有所關聯。同理 BmwBussinessCar 和 BenzSportsCar 也是一個產品族。

·可以說,抽象工廠模式和工廠方法模式的區別就在於需要創建對象的複雜程度上。而且抽象工廠模式是三個裏面最爲抽象、最具一般性的。

·抽象工廠模式的用意爲:給客戶端提供一個接口,可以創建多個產品族中的產品對象

·而且使用抽象工廠模式還要滿足下條件:

系統中有多個產品族,而系統一次只可能消費其中一族產品。

同屬於同一個產品族的產品以其使用。

·來看看抽象工廠模式的各個角色(和工廠方法的如出一轍):

抽象工廠角色: 這是工廠方法模式的核心,它與應用程序無關。是具體工廠角色必須實現的接口或者必須繼承的父類。 java 中它由抽象類或者接口來實現

具體工廠角色:它含有和具體業務邏輯有關的代碼。由應用程序調用以創建對應的具體產品的對象。 java 中它由具體的類來實現。

抽象產品角色:它是具體產品繼承的父類或者是實現的接口。 java 中一般有抽象類或者接口來實現。

具體產品角色:具體工廠角色所創建的對象就是此角色的實例。 java 中由具體的類來實現。 




2. 工廠模式應用

2.1 初始代碼:

/*
 * 髮型接口
 */
public interface HairInterface {
   //實現了髮型
	public void draw();
}
/*
 * 實現左偏分發型
 */
public class LeftHair implements HairInterface{
	//畫了一個左偏分發型
	public void draw() {
		System.out.println("左偏分發型");
	}
}
/*
 * 實現右偏分發型
 */
public class RightHair implements HairInterface {
	//畫了一個右偏分發型
	public void draw() {
		System.out.println("右偏分發型");
	}
}
public class Test {
    public static void main(String[] args) {
    	HairInterface left=new LeftHair();
    	left.draw();
    	HairInterface right=new RightHair();
    	right.draw();
    }
}
輸出:

左偏分發型

右偏分發型

2.2 改進一:(加入工廠類)

/*
 * 髮型工廠
 */
public class HairFactory {
     //根據類型來創建對象
	public HairInterface getHair(String key) {
		if("left".equals(key))
			return new LeftHair();
		else if("right".equals(key))
			return new RightHair();
		return null;
	}
}
//此時測試類即相應發生改變:
public class Test {
    public static void main(String[] args) {
//    	HairInterface left=new LeftHair();
//    	left.draw();
//    	HairInterface right=new RightHair();
//    	right.draw();
    	HairFactory factory=new HairFactory();
    	HairInterface left=factory.getHair("left");
    	left.draw();
    	HairInterface right=factory.getHair("right");
    	right.draw();
    }
}

此時做到了產品實現和客戶端相分離。

2.3 改進二:

加入反射,根據類的名稱來創建實例對象

package com.imooc.pattern.factory;
/*
 * 髮型工廠
 */
public class HairFactory {
	//根據類的名稱來創建對象
	public HairInterface getHairByClass(String className) throws Exception{
    	HairInterface hair=(HairInterface)Class.forName(className).newInstance();
    	return hair;
    }
}
//此時的測試類應該做出相應的改變:
public class Test {
    public static void main(String[] args) {	
    	HairFactory factory=new HairFactory();
    	try {
			HairInterface hair=factory.getHairByClass("com.imooc.pattern.factory.LeftHair");
		    hair.draw();
    	} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }
}

輸出:

左偏分發型

  此時如果增加了一個髮型的類,只需要創建該類,並在Test程序(客戶端)中調用該類的類名,即可實現,無需修改HairFactory類中的內容。

    此時有個問題就是類名(包括路徑名)太長,不直觀。可以用映射來解決。

2.4 改進三

  加入Properties文件,進行映射。Properties文件是java特有的屬性文件,以key-value的形式來存儲數據。用等號連接左右,左邊是key,右邊是value。如下:


創建一個類PropertiesReader來讀取type.properties文件中的內容。


/*
 * Properties文件的讀取類
 */
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class PropertiesReader {

	public Map<String, String> getProperties(){
		Properties pros=new Properties();
		Map<String, String> map=new HashMap<String,String>();
		
		try {
			InputStream in=getClass().getResourceAsStream("type.properties");
			pros.load(in);
			Enumeration en=pros.propertyNames();
			while(en.hasMoreElements()) {
				String key=(String)en.nextElement();
				String property=pros.getProperty(key);
				map.put(key, property);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}		
		return map;
	}
}
//修改Factory類:
import java.util.Map;
/*
 * 髮型工廠
 */
public class HairFactory {
	//根據類的名稱來創建對象
	public HairInterface getHairByClassKey(String key) throws Exception{
    	Map map=new PropertiesReader().getProperties();
    	String className=(String)map.get(key);
		HairInterface hair=(HairInterface)Class.forName(className).newInstance();
    	return hair;
    }
}
//Test類修改:
public class Test {
    public static void main(String[] args) {
    	try {
			HairInterface hair=factory.getHairByClassKey("left");
		    hair.draw();
    	} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 	
    }
}

輸出:

左偏分發型

如果此時增加一種髮型,如InHair

public class InHair implements HairInterface {
	public void draw() {
		System.out.println("中分發型");
	}
}

此時如果要使用這個類,需要進行的修改有:

①在type.properties文件中增加鍵值對映射:

in=com.imooc.pattern.factory.InHair

②在Test類(客戶端)調用該key

     HairInterface hair=factory.getHairByClassKey("in");

輸出:

中分發型

此時的擴展不需要改變Factory類,變得容易多了。

此時對下圖做對應:

Test類即客戶端,HairFactory類對應creater, HairInterface接口對應Iproduct, LeftHair RightHairInHair類對應下面的product1,product2,product3.

3. 抽象工廠模式應用

3.1 人物實現接口(抽象工廠 Factory

package com.imooc.pattern.factory;
/*
 * 人物實現接口
 */
public interface CharacterFactory {
    public Boy getBoy();
    public Girl getGirl();
}

3.2 具體實現工廠(ConcreteFactory)

package com.imooc.pattern.factory;
/*
 * 聖誕系列加工廠
 */
public class ChristmasCharacterFactory implements CharacterFactory {

	public Boy getBoy() {
		return new ChristmasBoy();
	}

	public Girl getGirl() {
		return new ChristmasGirl();
	}
}
package com.imooc.pattern.factory;
/*
 * 新年系列加工廠
 */
public class NewYearCharacterFactory implements CharacterFactory {

	public Boy getBoy() {
		return new NewYearBoy();
	}

	public Girl getGirl() {
		return new NewYearGirl();
	}

}

3.3 人物實現接口(產品族 AbstractProduct Interface

/*
 * 男孩
 */
public interface Boy {
    public void drawMan();
}
/*
 * 女孩
 */
public interface Girl {
    public void drawWoman();
}

3.4 具體人物實現(ConcreteProduct)

/*
 * 聖誕系列男孩
 */
public class ChristmasBoy implements Boy {
	public void drawMan() {
		System.out.println("聖誕系列男孩");
	}
}
/**
 * 聖誕系列女孩
 */
public class ChristmasGirl implements Girl {
	public void drawWoman() {
		System.out.println("聖誕系列女孩");
	}

}
/*
 * 新年系列男孩
 */
public class NewYearBoy implements Boy {
	public void drawMan() {
		System.out.println("新年系列男孩");
	}
}
/*
 * 新年系列女孩
 */
public class NewYearGirl implements Girl {
	public void drawWoman() {
		System.out.println("新年系列女孩");
	}
}

對應以下類圖

Test對應ClientcharacterFactory接口對應FactoryChristmasCharacterFactory類和NewYearCharacterFactory類對應ConcreteFactoryBoy接口和Girl接口對應AbstractProduct

NewYearBoyNewYearGirlChristmasBoyChristmasGirl類對應ConcreteProduct

4.總結



工廠方法模式和抽象工廠模式對比

·工廠模式是一種極端情況的抽象工廠模式,而抽象工廠模式可以看成是工廠模式的推廣。

·工廠模式用來創建一個產品的等級結構,而抽象工廠模式是用來創建多個產品的等級結構

·工廠模式只有一個抽象產品類,而抽象工廠模式有多個抽象產品類

 




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章