1. 程式人生 > >設計模式實戰應用之五:工廠方法模式

設計模式實戰應用之五:工廠方法模式

fontsize -c iterator name 工廠方法 iss sat cep exce

工廠方法模式的定義
工廠方法模式的應用相當廣泛。

工廠方法模式在 Java API 中的應用比比皆是:java.util.Collection 接口的 iterator 方法就是一個非常著名的工廠方法模式的演示樣例;java.net.URLStreamHandlerFactory 的 createURLStreamHandler(String protocol) 也是工廠方法模式的一個非常經典的應用,URLStreamHandlerFactory 定義了一個用來創建 URLStreamHandler 實例的 createURLStreamHandler 方法,詳細怎麽創建?詳細實現類說了算;此外。Java API 中工廠方法模式應用樣例還有 java.net.ContentHandlerFactory、java.net.URL 的 openConnection 方法……。

Gof 把工廠方法模式歸類到對象創建型模式,《設計模式:可復用面向對象軟件的基礎》對工廠方法模式做出了明白的定義:"Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses." 翻譯過來就是:"為創建一個對象定義一個接口,但把詳細創建哪個類的實例留給子類決定。工廠方法同意將一個類的初始化延遲到自己的子類。
"
Why 工廠方法模式?
通過使用工廠方法創建對象。我們能夠避免client代碼依賴於它所使用的接口的詳細實現。client不再須要把對象創建的構造方法註入到自己的代碼中去。client僅僅須要調用工廠,由工廠來決定符合要求的子類的創建過程。此外,工廠方法還能夠滿足每次構造不同對象、對象的構造非常復雜、對象的構造依賴詳細環境等等需求。
工廠方法模式的使用場合
  • client不知道它要創建的詳細是哪一個子類。
  • 一個類想要由自己的子類來定義某對象的創建過程。
  • 類將創建某對象的職責代理給一些幫助子類中的一個,並且你想要將哪一個子類作為代理的信息進行局部化。
工廠方法模式 VS 簡單工廠模式
  • 簡單工廠又稱"靜態工廠",顧名思義。這是工廠方法和簡單工廠最大的不同,這也是最easy區分開這兩種模式的特征。

  • 盡管兩者都誌在對象創建的局部封裝。但簡單工廠側重於創建對象的代碼復用,或者已創建實例的復用。或者創建實例的統一性;而工廠方法側重於子類自己特定創建邏輯的實現。

  • 簡單工廠模式中的工廠類是為產品類實例化的核心,而工廠方法模式把初始化工作交給子類實現。

    換句話講,假設有新產品加入,簡單工廠須要改動自己的工廠類。而工廠方法僅僅需添加新工廠子類 - 簡單工廠對 OCP 原則的支持力度不如工廠方法。

《Java Web 應用調用 C++ 加解密方法》需求
眾所周知,Java 對文件的加解密效能不如底層的 C/C++。

為了提高程序性能,我們要用 Java 進行 JNI 調用底層的加解密方法。
《Java Web 應用調用 C++ 加解密方法》分析
既然牽涉底層調用。就應該考慮到程序跨平臺的問題。另外,應用至少要能支持 Linux 和 Windows。以後還可能會部署在 mac。並且這些平臺還分為 64 位和 32 位。因此我們要為每一個平臺都要準備一個加解密底層庫,由於底層庫位數不同並不兼容,所以每一個平臺下又要有兩個庫:32 位一個,64 位一個。
每一個庫都應該有對應的類進行載入,比方本文工廠方法模式演示樣例中的詳細產品類之中的一個的 com.defonds.cloud.tool.config.encrypt.EncryptorLinuxAmd64.java。也就是我們的加解密類,這個類寫完並編譯好之後,使用 shell 生成 com_defonds_cloud_tool_config_encrypt_EncryptorLinuxAmd64.h 頭文件,C/C++ 程序猿依據每一個平臺的頭文件封裝加解密庫。


加解密類的實例化。我們就用工廠方法模式管理起來。
《Java Web 應用調用 C++ 加解密方法》類設計
技術分享
《Java Web 應用調用 C++ 加解密方法》源代碼
client類 EncryptorUtil 源代碼:

package com.defonds.cloud.tool.config.util;


import com.defonds.cloud.tool.config.encrypt.Encryptor;
import com.defonds.cloud.tool.config.encrypt.factory.EncryptorFactory;
import com.defonds.cloud.tool.config.encrypt.factory.EncryptorFactoryLinux;
import com.defonds.cloud.tool.config.encrypt.factory.EncryptorFactoryMac;
import com.defonds.cloud.tool.config.encrypt.factory.EncryptorFactoryWindows;


public class EncryptorUtil {
	private static String osName = System.getProperties().getProperty("os.name");
	private static Encryptor encryptor;
	static {
		EncryptorFactory encryptorFactory;
		if (osName.equals("Linux")) { // os is linux
			encryptorFactory = new EncryptorFactoryLinux();
		} else if (osName.contains("Windows")) { // os is windows
			encryptorFactory = new EncryptorFactoryWindows();
		} else { // os is mac
			encryptorFactory = new EncryptorFactoryMac();
		}
		encryptor = encryptorFactory.getEncryptor();
	}
	
	/**
	 * 
	 * @param content - the string to be encrypt or decrypt
	 * @param flag - encrypt flag, true is encrypt, false is decrypt
	 * @return - string after encrypt or decrypt
	 */
	public static String encryptorStr(String content, boolean flag) {
		return EncryptorUtil.encryptor.encrypt(content, flag);
	}
	
}

產品 Encryptor 接口:
package com.defonds.cloud.tool.config.encrypt;


public interface Encryptor {
	/**
	 * @param content str to be encrypted
	 * @param flag true:encrypt false:decrypt
	 * @return
	 */
	public String encrypt(String content, boolean flag);
}

詳細產品類之中的一個 EncryptorLinuxAmd64 源代碼(依據 EncryptorLinuxAmd64 寫好的 libaesjni.so 庫已放在 classpath 下的 /com/defonds/cloud/tool/config/encrypt/native/linux/amd64 文件夾中):
package com.defonds.cloud.tool.config.encrypt;


import java.io.IOException;


import com.defonds.cloud.tool.config.util.NativeUtils;


public class EncryptorLinuxAmd64 implements Encryptor {
	// Native method declaration
    // use the keyword "native" indicate this is an ‘unsafe‘ mtehod for java
    native String encryptStr(String content, boolean flag);
    
    // Load the library
    static {
        try {
    		System.loadLibrary("libaesjni");
    	}catch (UnsatisfiedLinkError e) {
            try {
				NativeUtils.loadLibraryFromJar("/com/defonds/cloud/tool/config/encrypt/native/linux/amd64/libaesjni.so");
			} catch (IOException e1) {
				throw new RuntimeException(e1);
			} // during runtime. .DLL within .JAR
    	}
    }


	@Override
	public String encrypt(String content, boolean flag) {
		// TODO Auto-generated method stub
		return this.encryptStr(content, flag);
	}
}

工廠接口 EncryptorFactory:
package com.defonds.cloud.tool.config.encrypt.factory;


import com.defonds.cloud.tool.config.encrypt.Encryptor;


public interface EncryptorFactory {
	public Encryptor getEncryptor();
}

詳細工廠類之中的一個 EncryptorFactoryLinux 源代碼:
package com.defonds.cloud.tool.config.encrypt.factory;


import com.defonds.cloud.tool.config.encrypt.Encryptor;
import com.defonds.cloud.tool.config.encrypt.EncryptorLinuxAmd64;
import com.defonds.cloud.tool.config.encrypt.EncryptorLinuxI386;


public class EncryptorFactoryLinux implements EncryptorFactory {
	private static String osArch = System.getProperties().getProperty("os.arch");
	
	@Override
	public Encryptor getEncryptor() {
		if (osArch.equals("amd64")) { // os is linux amd64
			return new EncryptorLinuxAmd64();
		} else { // os is linux i386
			return new EncryptorLinuxI386();
		}
	}


}

《Java Web 應用調用 C++ 加解密方法》測試
測試代碼例如以下:
		String abc = "abc";
		System.out.println("before encrypt:" + abc);
		String abcEncrypted = EncryptorUtil.encryptorStr(abc, true);
		System.out.println("after encrypt:" + abcEncrypted);
		String abc2 = EncryptorUtil.encryptorStr(abcEncrypted, false);
		System.out.println("after decrypt:" + abc2);

執行結果:
before encrypt:abc
after encrypt:3a5dd7db74fdab404e980805b1998e81
after decrypt:abc

後記 1
讀者或許會發現。《Java Web 應用調用 C++ 加解密方法》用簡單工廠模式也全然能夠實現,甚至可能效果會更好(由於不用每次都 new 工廠對象了):
public class EncryptorFactory {
	private static Properties sp = System.getProperties(); 
	private static String osName = sp.getProperty("os.name");
	private static String osArch = sp.getProperty("os.arch");
	private static int index2 = osName.indexOf("indows");
	private static boolean isWindows = false;
	static {
		if (index2 != -1) {
			isWindows = true;
		} 
    }
	
	public static Encryptor getEncryptor() {
		if (isWindows) { // os is windows
			if (osArch.equals("amd64")) { // os is windows amd64
				return new EncryptorWindowsAmd64();
			} else { // os is windows x86
				return new EncryptorWindowsX86();
			}
		} else { // os is linux
			if (osArch.equals("amd64")) { // os is linux amd64
				return new EncryptorLinuxAmd64();
			} else { // os is linux i386
				return new EncryptorLinuxI386();
			}
		}
	}
}

本來嘛,簡單工廠就是一個工廠方法的特例,GOF 甚至在 23 種設計模式裏面就沒有對其進行說明,並且非常多文獻裏面也沒有簡單工廠的概念。那麽究竟什麽時候用簡單工廠呢?筆者建議,假設你能預測到所有產品類的情況。就用簡單工廠。否則就工廠方法


後記 2
執行 jni 時,假設遇到相似於 java.lang.UnsatisfiedLinkError: /tmp/libaesjni_linux6345613888084265218.so: /tmp/libaesjni_linux6345613888084265218.so: wrong ELF class: ELFCLASS32 (Possible cause: architecture word width mismatch) 的錯誤:
Exception in thread "main" java.lang.UnsatisfiedLinkError: /tmp/libaesjni_linux6345613888084265218.so: /tmp/libaesjni_linux6345613888084265218.so: wrong ELF class: ELFCLASS32 (Possible cause: architecture word width mismatch)
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1747)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1643)
at java.lang.Runtime.load0(Runtime.java:787)
at java.lang.System.load(System.java:1022)
at com.defonds.cloud.tool.config.util.NativeUtils.loadLibraryFromJar(NativeUtils.java:91)
at com.defonds.cloud.tool.config.encrypt.EncryptorLinux.<clinit>(EncryptorLinux.java:20)
at com.defonds.cloud.tool.config.util.EncryptorFactory.getEncryptor(EncryptorFactory.java:24)
at com.defonds.cloud.tool.config.ConfigUtil.<clinit>(ConfigUtil.java:7)
at TestTime.main(TestTime.java:26)

解決的方法:分別提供 64 位和 32 位庫。
後記 3
假設 jni 時遇到下面錯誤:
java: symbol lookup error: /tmp/libaesjni4570314921835538044.so: undefined symbol: aesrun
java: symbol lookup error: /tmp/libaesjni8667398299924347273.so: undefined symbol: GetStringUTFChars

解決的方法:這是 C++ 調用了 C 的函數,編譯規則不一樣造成,把 C++ 代碼 (cpp 後綴) 所有換成 C 代碼,又一次編譯就好了。
參考資料

  • C++ 與 Java 混合編程

設計模式實戰應用之五:工廠方法模式