設計模式實戰應用之五:工廠方法模式
工廠方法模式的應用相當廣泛。
工廠方法模式在 Java API 中的應用比比皆是:java.util.Collection 接口的 iterator 方法就是一個非常著名的工廠方法模式的演示樣例;java.net.URLStreamHandlerFactory 的 createURLStreamHandler(String protocol) 也是工廠方法模式的一個非常經典的應用,URLStreamHandlerFactory 定義了一個用來創建 URLStreamHandler 實例的 createURLStreamHandler 方法,詳細怎麽創建?詳細實現類說了算;此外。Java API 中工廠方法模式應用樣例還有 java.net.ContentHandlerFactory、java.net.URL 的 openConnection 方法……。
Why 工廠方法模式?
通過使用工廠方法創建對象。我們能夠避免client代碼依賴於它所使用的接口的詳細實現。client不再須要把對象創建的構造方法註入到自己的代碼中去。client僅僅須要調用工廠,由工廠來決定符合要求的子類的創建過程。此外,工廠方法還能夠滿足每次構造不同對象、對象的構造非常復雜、對象的構造依賴詳細環境等等需求。
工廠方法模式的使用場合
- client不知道它要創建的詳細是哪一個子類。
- 一個類想要由自己的子類來定義某對象的創建過程。
- 類將創建某對象的職責代理給一些幫助子類中的一個,並且你想要將哪一個子類作為代理的信息進行局部化。
- 簡單工廠又稱"靜態工廠",顧名思義。這是工廠方法和簡單工廠最大的不同,這也是最easy區分開這兩種模式的特征。
- 盡管兩者都誌在對象創建的局部封裝。但簡單工廠側重於創建對象的代碼復用,或者已創建實例的復用。或者創建實例的統一性;而工廠方法側重於子類自己特定創建邏輯的實現。
- 簡單工廠模式中的工廠類是為產品類實例化的核心,而工廠方法模式把初始化工作交給子類實現。
換句話講,假設有新產品加入,簡單工廠須要改動自己的工廠類。而工廠方法僅僅需添加新工廠子類 - 簡單工廠對 OCP 原則的支持力度不如工廠方法。
眾所周知,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 混合編程
設計模式實戰應用之五:工廠方法模式