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

來源:互聯網
上載者:User

標籤: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 代碼,重新編譯就好了。
        參考資料
  • C++ 與 Java 混合編程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.