首页 > 代码库 > 设计模式实战应用之五:工厂方法模式
设计模式实战应用之五:工厂方法模式
工厂方法模式的定义
工厂方法模式的应用相当广泛。工厂方法模式在 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 工厂方法模式?
通过使用工厂方法创建对象,我们可以避免客户端代码依赖于它所使用的接口的具体实现。客户端不再需要把对象创建的构造方法注入到自己的代码中去,客户端只需要调用工厂,由工厂来决定符合要求的子类的创建过程。此外,工厂方法还可以满足每次构造不同对象、对象的构造很复杂、对象的构造依赖具体环境等等需求。
工厂方法模式的使用场合
众所周知,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++ 加解密方法》源码
客户端类 EncryptorUtil 源码:
产品 Encryptor 接口:
具体产品类之一 EncryptorLinuxAmd64 源码(根据 EncryptorLinuxAmd64 写好的 libaesjni.so 库已放在 classpath 下的 /com/defonds/cloud/tool/config/encrypt/native/linux/amd64 目录中):
工厂接口 EncryptorFactory:
具体工厂类之一 EncryptorFactoryLinux 源码:
《Java Web 应用调用 C++ 加解密方法》测试
测试代码如下:
运行结果:
before encrypt:abc
after encrypt:3a5dd7db74fdab404e980805b1998e81
after decrypt:abc
后记 1
读者也许会发现,《Java Web 应用调用 C++ 加解密方法》用简单工厂模式也完全可以实现,甚至可能效果会更好(因为不用每次都 new 工厂对象了):
本来嘛,简单工厂就是一个工厂方法的特例,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 代码,重新编译就好了。
参考资料
工厂方法模式的应用相当广泛。工厂方法模式在 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 工厂方法模式?
通过使用工厂方法创建对象,我们可以避免客户端代码依赖于它所使用的接口的具体实现。客户端不再需要把对象创建的构造方法注入到自己的代码中去,客户端只需要调用工厂,由工厂来决定符合要求的子类的创建过程。此外,工厂方法还可以满足每次构造不同对象、对象的构造很复杂、对象的构造依赖具体环境等等需求。
工厂方法模式的使用场合
- 客户端不知道它要创建的具体是哪一个子类。
- 一个类想要由自己的子类来定义某对象的创建过程。
- 类将创建某对象的职责代理给一些帮助子类中的一个,并且你想要将哪一个子类作为代理的信息进行局部化。
- 简单工厂又称"静态工厂",顾名思义,这是工厂方法和简单工厂最大的不同,这也是最容易区分开这两种模式的特征。
- 虽然两者都志在对象创建的局部封装,但简单工厂侧重于创建对象的代码复用,或者已创建实例的复用,或者创建实例的统一性;而工厂方法侧重于子类自己特定创建逻辑的实现。
- 简单工厂模式中的工厂类是为产品类实例化的核心,而工厂方法模式把初始化工作交给子类实现。换句话讲,如果有新产品添加,简单工厂需要修改自己的工厂类,而工厂方法只需增加新工厂子类 - 简单工厂对 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++ 加解密方法》源码
客户端类 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 混合编程
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。