今天学习分享的是工厂模式:

将创建对象移交给工厂来处理。
大部分工厂类都是以 “Factory” 这个单词结尾的,但也不是必须的,比如 Java 中的 DateFormat、Calender。 除此之外,工厂类中创建对象的方法一般都是 create 开头,比如代码中的 createParser(), 但有的也命名为 getInstance()、createInstance()、newInstance(), 有的甚至命名为 valueOf()(比如 Java String 类的 valueOf() 函数)等等。
GoF 把简单工厂并入工厂方法,这里单独摘出来成为一部分:

工厂模式
简单工厂模式描述了一个类,它拥有一个包含大量条件语句的构建方法,可根据方法的参数来选择对何种产品进行初始化并将其返回。
当每个对象的创建逻辑都比较简单的时候,将多个对象的创建逻辑放到一个工厂类中。
优点:
简单用 if 判断参数,并生成对象给调用者。
public class CollectionFactory {
public static Collection create(String type) throws Exception {
switch (type) {
case "list":
return new ArrayList();
case "linked":
return new LinkedList();
case "deque":
return new ArrayDeque();
default:
throw new Exception("传递的用户类型错误。");
}
}
}
如果对象是可复用的,也可以把对象事先创建好并缓存起来:
public class RuleConfigParserFactory {
private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>();
static {
cachedParsers.put("json", new JsonRuleConfigParser());
cachedParsers.put("xml", new XmlRuleConfigParser());
cachedParsers.put("yaml", new YamlRuleConfigParser());
cachedParsers.put("properties", new PropertiesRuleConfigParser());
}
public static IRuleConfigParser createParser(String configFormat) {
if (configFormat == null || configFormat.isEmpty()) {
return null;//返回null还是IllegalArgumentException全凭你自己说了算
}
IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
return parser;
}
}
JDK类库中广泛使用了简单工厂模式,如工具类 java.text.DateFormat,它用于格式化一个本地日期或者时间。
java.text.DateFormat#getDateInstance()
java.text.DateFormat#getDateInstance(int style)
java.text.DateFormat#getDateInstance(int style,Locale locale)
密钥生成器:
javax.crypto.KeyGenerator#getInstance(String algorithm)
创建密码器:
javax.crypto.Cipher#getInstance(String transformation);
工厂方法是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
Head First 定义:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类
当每个对象的 创建逻辑 都比较 复杂 的时候,
为了避免设计一个过于庞大的简单工厂类时,将创建逻辑拆分得更细,
让每个对象的创建逻辑独立到各自的工厂类中。
工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用。
对象仍将通过 new 运算符创建,只是该运算符改在工厂的方法中调用罢了。
优点:
缺点:
工厂接口与工厂
public interface ICollectionFactory {
Collection createCollection();
}
public class ListFactory implements ICollectionFactory {
@Override
public Collection createCollection() {
// 可以把复杂的初始化过程放在这里
return new ArrayList();
}
}
public class LinkedFactory implements ICollectionFactory {
@Override
public Collection createCollection() {
// 可以把复杂的初始化过程放在这里
return new LinkedList();
}
}
public class DequeFactory implements ICollectionFactory {
@Override
public Collection createCollection() {
// 可以把复杂的初始化过程放在这里
return new ArrayDeque();
}
}
工厂的工厂:
public class CollectionFactoryMap {
private static final Map<String, ICollectionFactory> cachedFactories = new HashMap<>();
static {
cachedFactories.put("list", new ListFactory());
cachedFactories.put("linked", new LinkedFactory());
cachedFactories.put("deque", new DequeFactory());
}
public static ICollectionFactory getCollectionFactory(String type) {
if (type == null || type.isEmpty()) {
return null;
}
ICollectionFactory factory = cachedFactories.get(type.toLowerCase());
return factory;
}
}
JDK (这里有些是简单工厂,具体判断是看把有没有实例化推迟到子类):
java.util.ResourceBundle#getBundle()
java.text.NumberFormat#getInstance()
java.nio.charset.Charset#forName()
java.net.URLStreamHandlerFactory#createURLStreamHandler(String) (根据协议返回不同的单例对象)
java.util.EnumSet#of()
javax.xml.bind.JAXBContext#createMarshaller() 及其他类似的方法。
这个是简单工厂:
java.util.Calendar#getInstance()
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
//...
public static Calendar getInstance(TimeZone zone, Locale aLocale){
return createCalendar(zone, aLocale);
}
private static Calendar createCalendar(TimeZone zone,Locale aLocale) {
CalendarProvider provider = LocaleProviderAdapter.getAdapter(
CalendarProvider.class, aLocale).getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
//...
}
这个是工厂方法:
org.slf4j.LoggerFactory#getILoggerFactory()
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
抽象工厂是一种创建型设计模式,它能创建一系列相关或相互依赖的对象,而无需指定其具体类。
Head First 定义:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类
优点:
缺点:
王争老师举的例子:
前面简单工厂提到的可复用写法 RuleConfigParserFactory,通过不同文件类型加载解析器。
如果多出了分类,如系统解析类、规则解析类等,增加的工厂数量将倍增:
针对规则配置的解析器:基于接口IRuleConfigParser
JsonRuleConfigParser
XmlRuleConfigParser
YamlRuleConfigParser
PropertiesRuleConfigParser
针对系统配置的解析器:基于接口ISystemConfigParser
JsonSystemConfigParser
XmlSystemConfigParser
YamlSystemConfigParser
PropertiesSystemConfigParser
让一个工厂负责创建多个不同类型的对象(IRuleConfigParser、ISystemConfigParser 等),而不是只创建一种 parser 对象。这样就可以有效地减少工厂类的个数。具体的代码实现如下所示:
public interface IConfigParserFactory {
IRuleConfigParser createRuleParser();
ISystemConfigParser createSystemParser();
//此处可以扩展新的parser类型,比如IBizConfigParser
}
public class JsonConfigParserFactory implements IConfigParserFactory {
@Override
public IRuleConfigParser createRuleParser() {
return new JsonRuleConfigParser();
}
@Override
public ISystemConfigParser createSystemParser() {
return new JsonSystemConfigParser();
}
}
public class XmlConfigParserFactory implements IConfigParserFactory {
@Override
public IRuleConfigParser createRuleParser() {
return new XmlRuleConfigParser();
}
@Override
public ISystemConfigParser createSystemParser() {
return new XmlSystemConfigParser();
}
}
// 省略YamlConfigParserFactory和PropertiesConfigParserFactory代码
这里是我参考 refactoringguru 实现的例子:
refactoringguru 文中示例的是 “跨平台 UI 组件”,目的为适配多平台下 UI 组件的渲染,结构为:
而我的文件结构为:
大体思路是:
算法题中有很多数据结构类型,比如数组,链表,树等结构。这边假设要实现一个可视化的数据结构。
上面简单工厂实现了一个简单容器工厂 CollectionFactory,用来创建不同的数据类型。
到了这里,结构有了,想要输出成 html 动态显示,或者输出成 pdf 打印版,于是利用抽象工厂来实现:
/**
* 【抽象工厂接口】声明了一组能返回不同抽象产品的方法。
* 这些产品属于同一个系列且在高层主题或概念上具有相关性。
* 同系列的产品通常能相互搭配使用。
* 系列产品可有多个变体,但不同变体的产品不能搭配使用。
*/
public interface PrinterFactory {
MyArrayList createArrayList();
MyLinkedList createLinkedList();
// 这里可以扩展类型
}
/**
* 【具体工厂】可生成属于同一变体的系列产品。
* 工厂会确保其创建的产品能相互搭配使用。
* 具体工厂方法签名会返回一个抽象产品,但在方法内部则会对具体产品进行实例化。
*/
public class PrinterHTMLFactory implements PrinterFactory {
@Override
public MyArrayList createArrayList() {
return new MyArrayListHTML();
}
@Override
public MyLinkedList createLinkedList() {
return new MyLinkedListHTML();
}
// 这里可以扩展类型
}
/**
* 每个【具体工厂】中都会包含一个相应的产品变体。
*/
public class PrinterPDFFactory implements PrinterFactory {
@Override
public MyArrayList createArrayList() {
return new MyArrayListPDF();
}
@Override
public MyLinkedList createLinkedList() {
return new MyLinkedListPDF();
}
// 这里可以扩展类型
}
/**
* 系列产品中的特定产品必须有一个基础【产品接口】。所有产品变体都必须实现这个接口。
*/
public interface MyArrayList {
void print();
}
/**
* 【具体产品】由相应的具体工厂创建。
*/
public class MyArrayListHTML implements MyArrayList{
@Override
public void print() {
System.out.println("打印ArrayList样式的HTML");
}
}
/**
* 这是另一个产品。
* 所有产品都可以互动,但是只有相同具体变体的产品之间才能够正确地进行交互。
*/
public class MyArrayListPDF implements MyArrayList{
@Override
public void print() {
System.out.println("打印ArrayList样式的PDF");
}
}
// MyLinkedList 同上,略
// MyLinkedListHTML 同上,略
// MyLinkedListPDF 同上,略
/**
* 【客户端】代码仅通过抽象类型(PrinterFactory、arrayList 和 LinkedLlist)使用工厂和产品。
* 这让你无需修改任何工厂或产品子类就能将其传递给客户端代码。
*/
public class Application {
private PrinterFactory factory;
private MyArrayList arrayList;
private MyLinkedList linkedList;
public Application(PrinterFactory factory) {
this.factory = factory;
}
public void create(){
arrayList = factory.createArrayList();
linkedList = factory.createLinkedList();
}
public void print() {
arrayList.print();
linkedList.print();
}
}
/**
* 【程序】会根据当前配置或环境设定选择工厂类型,并在运行时创建工厂(通常在初始化阶段)。
*/
public class ApplicationConfigurator {
public static void main(String[] args) throws Exception {
PrinterFactory factory;
if ("pdf".equals(args[0])) {
factory = new PrinterPDFFactory();
} else if ("html".equals(args[0])) {
factory = new PrinterHTMLFactory();
} else {
throw new Exception("未知的输出方式");
}
Application application = new Application(factory);
application.create();
application.print();
}
}
这样的话,如后续果我想扩展类型,或者开放给别人扩展,别人只需要扩展一个新的类型即可,不需要改动工厂。
以下是来自核心 Java 程序库的一些示例:
javax.xml.parsers.DocumentBuilderFactory#newInstance()
javax.xml.transform.TransformerFactory#newInstance()
javax.xml.xpath.XPathFactory#newInstance()
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//连接相应的数据库
Connection connection = DriverManager.getConnection("url", "username", "password");
这个私有 getConnection 方法中会遍历已经注册数据库驱动,
也就是我们加载的 MySQL 数据库驱动 Class.forName("com.mysql.jdbc.Driver")。
DriverManager#getConnection():
// private static Connection getConnection(String url, java.util.Properties info, Class<?> caller)
···
for(DriverInfo aDriver : registeredDrivers){
···
Connection con = aDriver.driver.connect(url, info);
···
}
代码中 aDriver.driver 返回的正是 MySQL 实现的具体驱动,
也就是一个具体的子工厂 com.mysql.jdbc.Driver,
它的抽象工厂接口则是 Java 提供的 com.sql.Driver。
Java 定义了一种 JDBC 连接数据库的方式和相应的接口,各个厂商只需要实现它即可。
客户端在选择使用哪种数据库的时候不用关心具体方法的调用或者返回哪一个类,只需要使用工厂中提供的工厂方法即可。
DI 容器:依赖注入容器(Dependency Injection Container)。
一个工厂类只负责某个类对象或者某一组相关类对象的创建,而 DI 容器负责的是整个应用中所有类对象的创建。
DI 容器底层最基本的设计思路就是基于工厂模式的。 DI 容器相当于一个大的工厂类,负责在程序启动的时候,根据配置(要创建哪些类对象,每个类对象的创建需要依赖哪些其他类对象)事先创建好对象。 当应用程序需要使用某个类对象的时候,直接从容器中获取即可。 正是因为它持有一堆对象,所以这个框架才被称为“容器”。