標籤:Factory 方法 jni Factory 方法模式 簡單工廠 原廠模式
Factory 方法模式的定義
Factory 方法模式的應用相當廣泛。Factory 方法模式在 Java API 中的應用比比皆是:java.util.Collection 介面的 iterator 方法就是一個很著名的抽象Factory 方法的樣本;java.net.URLStreamHandlerFactory 的 createURLStreamHandler(String protocol) 也是Factory 方法模式的一個很經典的應用,URLStreamHandlerFactory 定義了一個用來建立 URLStreamHandler 執行個體的 createURLStreamHandler 方法,具體怎麼建立?具體實作類別說了算;此外,Java API 中Factory 方法模式應用例子還有 java.net.ContentHandlerFactory、java.net.URL 的 openConnection 方法……。
Gof 把Factory 方法模式歸類到對象建立型模式,《設計模式:可複用物件導向軟體的基礎》對Factory 方法模式做出了明確的定義:"Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses." 翻譯過來就是:"
為建立一個對象定義一個介面,但把具體建立那個類的執行個體留給子類決定。Factory 方法允許一個類的初始化延遲到自己的子類。"
Why Factory 方法模式?
通過使用Factory 方法建立對象,我們可以避免用戶端代碼依賴於它所使用的介面的具體實現。用戶端不再需要把對象建立的構造方法注入到自己的代碼中去,用戶端只需要調用工廠,由工廠來決定符合要求的子類的建立過程。此外,Factory 方法還可以滿足每次構造不同對象、對象的構造很複雜、對象的構造依賴具體環境等等需求。
Factory 方法模式的使用場合
- 用戶端不知道它要建立的具體是哪一個子類。
- 一個類想要由自己的子類來定義某對象的建立過程。
- 類將建立某對象的職責代理給一些協助子類中的一個,並且你想要將哪一個子類作為代理的資訊進行局部化。
Factory 方法模式 VS 簡單原廠模式
- 簡單工廠又稱"靜態工廠",顧名思義,這是Factory 方法和簡單工廠最大的不同,這也是最容易區分開這兩種模式的特徵。
- 雖然兩者都志在對象建立的局部封裝,但簡單工廠側重於建立對象的代碼複用,或者已建立執行個體的複用,或者建立執行個體的統一性;而Factory 方法側重於子類自己特定建立邏輯的實現。
- 簡單原廠模式中的工廠類是為產品類執行個體化的核心,而Factory 方法模式把初始化工作交給子類實現。換句話講,如果有新產品添加,簡單工廠需要修改自己的工廠類,而Factory 方法只需增加新工廠子類 - 簡單工廠對 OCP 原則的支援力度不如Factory 方法。
《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++ 程式員根據每個平台的標頭檔封裝加解密庫。
加解密類的執行個體化,我們就用Factory 方法模式管理起來。
《Java Web 應用程式調用 C++ 加解密方法》類設計
《Java Web 應用程式調用 C++ 加解密方法》源碼
用戶端類 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 linuxencryptorFactory = new EncryptorFactoryLinux();} else if (osName.contains("Windows")) { // os is windowsencryptorFactory = new EncryptorFactoryWindows();} else { // os is macencryptorFactory = 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 } }@Overridepublic String encrypt(String content, boolean flag) {// TODO Auto-generated method stubreturn 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");@Overridepublic Encryptor getEncryptor() {if (osArch.equals("amd64")) { // os is linux amd64return new EncryptorLinuxAmd64();} else { // os is linux i386return 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 windowsif (osArch.equals("amd64")) { // os is windows amd64return new EncryptorWindowsAmd64();} else { // os is windows x86return new EncryptorWindowsX86();}} else { // os is linuxif (osArch.equals("amd64")) { // os is linux amd64return new EncryptorLinuxAmd64();} else { // os is linux i386return new EncryptorLinuxI386();}}}}
本來嘛,簡單工廠就是一個Factory 方法的特例,GOF 甚至在 23 種設計模式裡面就沒有對其進行說明,而且很多文獻裡面也沒有簡單工廠的概念。那麼到底什麼時候用簡單工廠呢?筆者建議,
如果你能預測到所有產品類的情況,就用簡單工廠,否則就Factory 方法。
後記 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 代碼,重新編譯就好了。
參考資料