Java設計模式--工廠方法模式&模擬工廠模式

概念

工廠模式就是實例化對象,用工廠方法來代替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();
	}

}

對比

  • 工廠模式是一種極端情況的抽象工廠模式,而抽象工廠模式可以看成是工廠模式的推廣
  • 工廠模式可以用來創建一個產品的等級結構,而抽象工廠模式是用來創建多個產品的等級結構
  • 工廠模式只有一個抽象產品類,而抽象工廠模式有多個抽象產品類

總結

工廠模式有什麼用:

  • 系統可以在不修改具體工廠角色的情況下引進新的產品
  • 客戶端不必關心對象如何創建,明確了職責
  • 更好的理解面向對象的原則:面向接口編程,而不要面向實現編程

工廠模式適用場景:

  • 一個系統應當不依賴於產品類實例被創建,組成和表示的細節。這對於所有形態的工廠模式都是重要的
  • 該系統的產品要有至少一個的產品族
  • 工廠模式的約束:同屬於同一個產品族的產品是設計成在一起使用的
  • 不同的產品以一系列的接口的面貌出現,從而使得系統不依賴於接口實現的細節
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章