概念
工廠模式就是實例化對象,用工廠方法來代替new操作,工廠模式包括工廠方法模式和抽象工廠模式,抽象工廠模式是工廠方法模式的拓展。
意圖
工廠模式的意圖就是定義一個接口來創建對象,但是讓子類來決定哪些類需要被實例化,也就是說工廠方法把實例化的工作推遲到子類中去實現。
適用場景
- 有一組類似的對象需要創建
- 在編碼時不能預見需要創建哪種類的實例
- 系統需要考慮擴展性,不應該依賴於產品類實例如何被創建、組合和表達的細節
在軟件中經常面臨着對象的創建工作,由於需求的變化,這個對象可能會隨之發生變化,但它卻擁有比較穩定的接口。爲此,我們需要提供一種封裝機制來隔離出這個易變對象的變化,從而保持系統中其他依賴該對象的對象不隨着需求變化而變化。所以我們要讓代碼設計的儘量鬆耦合,一個對象的依賴對象的變化與本身無關,並且具體產品與客戶端剝離,責任分割。
工廠方法模式代碼實現
根據我們的類圖,我們可以得到我們需要定義好一些接口以及實現類。
HairInterface.java
package com.xjh.factory;
/**
* 髮型接口
* @author Gin
*
*/
public interface HairInterface {
//實現髮型
public void draw();
}
LeftHair.java
package com.xjh.factory;
/**
* 左偏分發型
* @author Gin
*
*/
public class LeftHair implements HairInterface {
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println("左偏分發型");
}
}
RightHair.java
package com.xjh.factory;
/**
* 右偏分發型
* @author Gin
*
*/
public class RightHair implements HairInterface {
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println("右偏分發型");
}
}
客戶端Test.java
package com.xjh.factory;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
HairInterface left = new LeftHair();
left.draw();
}
}
其實在這裏我們就發現了我們還是想要一個實例就要自己手動new出一個,這是不符合的,所以我們就要開始定義工廠類了,通過工廠類來管理髮型。
所以我們創建出一個工廠類,然後返回HairInterface的實例,對於這個我們很當然的想到了通過switch來根據key來返回對象。
HairFactory.java
package com.xjh.factory;
/**
* 髮型工廠
* @author Gin
*
*/
public class HairFactory {
/**
* 根據類型來創建對象
*/
public HairInterface getHair(String key) {
switch(key){
case "left":
return new LeftHair();
case "right":
return new RightHair();
}
return null;
}
}
然後我們只要傳入參數就可以得到實例,這樣我們就把獲得實例分離開來了。
Test.java
package com.xjh.factory;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
HairFactory factory = new HairFactory();
HairInterface left = factory.getHair("left");
left.draw();
}
}
但是我們可以發現使用switch的時候,如果我們需要增加一個類就有需要添加一個key,並且來編寫相關代碼。所以我們就要使用到反射,通過類名來獲取實例。
HairFactory.java
/**
* 根據類名來創建對象
*/
public HairInterface getHairByClass(String className) {
try {
HairInterface hair = (HairInterface) Class.forName(className).newInstance();
return hair;
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
我們直接傳入完整的類名就好了。
package com.xjh.factory;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
HairFactory factory = new HairFactory();
HairInterface left = factory.getHairByClass("com.xjh.factory.LeftHair");
left.draw();
}
}
當然輸入包名我們會很不習慣,所以我們可以使用Java中的properties文件來進行映射,這樣我們就只要像上面一樣輸入簡稱就好了。
type.properties
left=com.xjh.factory.LeftHair
right=com.xjh.factory.RightHair
然後我們就要有讀取properties文件的類,其實就是將properties文件中的內容映射到map中去。
PropertiesReader.java
package com.xjh.factory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class PropertiesReader {
private static Map<String,String> map = null;
PropertiesReader(){}
public static Map<String,String> getProperties(){
if(map == null){
map = new HashMap<>();
Properties properties = new Properties();
InputStream in = PropertiesReader.class.getResourceAsStream("type.properties");
try {
properties.load(in);
Enumeration en = properties.propertyNames();
while (en.hasMoreElements()){
String key = (String) en.nextElement();
String value = properties.getProperty(key);
map.put(key,value);
}
} catch (IOException e) {
e.printStackTrace();
}
}
return map;
}
}
HairFactory.java
我們獲取Key的值,然後根據key到map中找到對應的包名,以後我們就只要編寫一個實體類,然後在type.properties中加入一條key與之對應的包名,就可以了,不需要修改其他代碼,這樣就降低了代碼的耦合度。
/**
* 根據類名的key來創建對象
*/
public HairInterface getHairByClassKey(String key) {
try {
Map<String,String> map = new PropertiesReader().getProperties();
HairInterface hair = (HairInterface) Class.forName(map.get(key)).newInstance();
return hair;
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
Test.java
這樣我們就直接輸入key就可以了。
HairFactory factory = new HairFactory();
HairInterface left = factory.getHairByClassKey("left");
left.draw();
模擬工廠模式實現
根據類圖中的流程,我們要有男女兩個接口,已方便拓展相應的系列,創建不同系列的男女,然後再根據系列的不同增加工廠,然後在客戶端調用該工廠就好了。
Boy.java
package com.xjh.factory;
/**
* 男孩
* @author Gin
*
*/
public interface Boy {
public void drawMan();
}
Girl.java
package com.xjh.factory;
/**
* 女孩
* @author Gin
*
*/
public interface Girl {
public void drawWomen();
}
HNBoy.java
package com.xjh.factory;
/**
* 新年男孩
* @author Gin
*
*/
public class HNBoy implements Boy {
@Override
public void drawMan() {
// TODO Auto-generated method stub
System.out.println("新年男孩");
}
}
HNGirl.java
package com.xjh.factory;
/**
* 新年女孩
* @author Gin
*
*/
public class HNGirl implements Girl {
@Override
public void drawWomen() {
// TODO Auto-generated method stub
System.out.println("新年女孩");
}
}
MCBoy.java
package com.xjh.factory;
/**
* 聖誕男孩
* @author Gin
*
*/
public class MCBoy implements Boy {
@Override
public void drawMan() {
// TODO Auto-generated method stub
System.out.println("聖誕男孩");
}
}
MCGirl.java
package com.xjh.factory;
/**
* 聖誕女孩
* @author Gin
*
*/
public class MCGirl implements Girl {
@Override
public void drawWomen() {
// TODO Auto-generated method stub
System.out.println("聖誕女孩");
}
}
HNFctory.java
package com.xjh.factory;
/**
* 新年系列工廠
* @author Gin
*
*/
public class HNFctory implements PersonFactory {
@Override
public Boy getBoy() {
// TODO Auto-generated method stub
return new HNBoy();
}
@Override
public Girl getGirl() {
// TODO Auto-generated method stub
return new HNGirl();
}
}
MCFctory.java
package com.xjh.factory;
/**
* 聖誕系列工廠
* @author Gin
*
*/
public class MCFctory implements PersonFactory {
@Override
public Boy getBoy() {
// TODO Auto-generated method stub
return new MCBoy();
}
@Override
public Girl getGirl() {
// TODO Auto-generated method stub
return new MCGirl();
}
}
然後我們在客戶端進行調用就好了,方法和工廠模式差不多。
Test.java
package com.xjh.factory;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
PersonFactory factory = new MCFactory();
Boy boy = factory.getBoy();
boy.drawMan();
}
}
對比
- 工廠模式是一種極端情況的抽象工廠模式,而抽象工廠模式可以看成是工廠模式的推廣
- 工廠模式可以用來創建一個產品的等級結構,而抽象工廠模式是用來創建多個產品的等級結構
- 工廠模式只有一個抽象產品類,而抽象工廠模式有多個抽象產品類
總結
工廠模式有什麼用:
- 系統可以在不修改具體工廠角色的情況下引進新的產品
- 客戶端不必關心對象如何創建,明確了職責
- 更好的理解面向對象的原則:面向接口編程,而不要面向實現編程
工廠模式適用場景:
- 一個系統應當不依賴於產品類實例被創建,組成和表示的細節。這對於所有形態的工廠模式都是重要的
- 該系統的產品要有至少一個的產品族
- 工廠模式的約束:同屬於同一個產品族的產品是設計成在一起使用的
- 不同的產品以一系列的接口的面貌出現,從而使得系統不依賴於接口實現的細節