工厂模式如此常用,是因为它相当于创建实例对象的 new。我们经常要根据类生成实例,如 A a = new A()。以后再写 new 时不妨多想一步:能不能改用工厂?虽然可能多写一点代码,但能换来更大的可扩展性和尽量少的修改量。
以类 Sample 为例,创建实例本来是 Sample sample = new Sample();。实际中创建时往往还要做初始化:赋值、查库等。参数简单时可以写成 new Sample(参数);初始化若是很长一段代码,塞进构造函数会很难看,也违背封装和分派:长代码应切割、封装,降低段与段之间的耦合。
一、为什么要把 new 换成工厂
创建实例和使用实例要分开:大量初始化工作应从 Sample 的构造函数里拆出去,这时就不能再用简单的 new Sample(参数)。若 Sample 有子类 MySample,按面向接口编程应把 Sample 抽象成接口,下面有 MySample 和 HisSample:
Sample mysample = new MySample();
Sample hissample = new HisSample();
项目深入后 Sample 还可能“生出很多儿子”,传统写法要对每个儿子 new 一次,甚至改以前的代码。一开始就用工厂,这些麻烦就没有了。
二、工厂方法
专门建一个生产 Sample 实例的工厂:
public class Factory {
public static Sample creator(int which) {
// getClass 产生 Sample,一般可使用动态类装载装入类
if (which == 1)
return new SampleA();
else if (which == 2)
return new SampleB();
return null;
}
}
程序里要实例化 Sample 时写成:
Sample sampleA = Factory.creator(1);
整段代码不再涉及 Sample 的具体子类,达到封装效果,也减少改错机会。通俗说法:具体事情做得越多越容易犯错;工厂把“怎么 new”藏起来,调用方只认接口。
使用工厂方法要注意几个角色:先定义产品接口(如 Sample),下面有实现类(如 SampleA),再有一个 factory 类用来生成产品。下图最右边是生产出的对象 Sample:

三、反射实现工厂方法
用 Class.forName(className).newInstance() 按类名动态装入产品,换实现类时不必改工厂内部的 if/else。原文里接口方法、赋值语句漏了分号,下面按可编译写法补全:
package com.scxh.java.factory;
interface Fruit { // 水果接口
public void eat(); // 吃水果
}
class Apple implements Fruit { // 定义苹果
public void eat() {
System.out.println("**吃苹果。");
}
}
class Orange implements Fruit {
public void eat() {
System.out.println("**吃橘子。");
}
}
class Factory {
public static Fruit getInstance(String className) {
Fruit fruit = null;
try {
fruit = (Fruit) Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return fruit;
}
}
public class FactoryDemo {
public static void main(String args[]) { // 通过工厂取得接口实例,传入完整的包.类名称
Fruit f = Factory.getInstance("com.scxh.java.factorydemo.Apple");
if (f != null) { // 判断是否取得接口实例
f.eat();
}
}
}
四、抽象工厂
工厂模式里有工厂方法(Factory Method)和抽象工厂(Abstract Factory)。差别在于创建对象的复杂程度。工厂方法只创建一种产品 Sample;若还有新产品接口 Sample2,且 Sample 有 SampleA / SampleB,Sample2 有 Sample2A / Sample2B,就把 Factory 做成抽象类,共同部分放抽象类,不同部分交给子类:
public abstract class Factory {
public abstract Sample creator();
public abstract Sample2 creator(String name);
}
public class SimpleFactory extends Factory {
public Sample creator() {
// ...
return new SampleA();
}
public Sample2 creator(String name) {
// ...
return new Sample2A();
}
}
public class BombFactory extends Factory {
public Sample creator() {
// ...
return new SampleB();
}
public Sample2 creator(String name) {
// ...
return new Sample2B();
}
}
两个工厂各自生产一套 Sample 和 Sample2。为什么不拆成两个工厂方法分别生产?关键是 SimpleFactory 里生产两种产品的方法有联系,要捆在一个类里——比如制造工艺都比较简单,所以叫 SimpleFactory。实际应用中工厂方法更多,而且常和动态类装入器一起用。
| 模式 |
产品族 |
典型用法 |
| 工厂方法 |
一种产品接口,多个实现 |
creator(which) 或 Class.forName 装入 |
| 抽象工厂 |
多组相关产品一起创建 |
同一工厂产出配套的 Sample + Sample2 |
| 简单工厂 |
静态方法集中 new |
改产品时主要改工厂类 |
五、Jive 的 ForumFactory
前面 Singleton 里讨论过这个例子,这里看工厂部分。现在的 Jive 用数据库存放帖子;若改成文件系统,只要换 className:
private static String className = "com.jivesoftware.forum.database.DbForumFactory";
用自己开发的创建 forum 的方法代替 DbForumFactory 即可。
public abstract class ForumFactory {
private static Object initLock = new Object();
private static String className = "com.jivesoftware.forum.database.DbForumFactory";
private static ForumFactory factory = null;
public static ForumFactory getInstance(Authorization authorization) {
if (authorization == null) {
return null;
}
// 以下使用了 Singleton 单态模式
if (factory == null) {
synchronized (initLock) {
if (factory == null) {
try {
// 动态装载类
Class c = Class.forName(className);
factory = (ForumFactory) c.newInstance();
} catch (Exception e) {
return null;
}
}
}
}
// 返回 proxy,用来限制授权对 forum 的访问
return new ForumFactoryProxy(authorization, factory,
factory.getPermissions(authorization));
}
// 真正创建 forum 的方法由继承 ForumFactory 的子类去完成
public abstract Forum createForum(String name, String description)
throws UnauthorizedException, ForumAlreadyExistsException;
}
这段代码一共用了三种模式:工厂、Singleton,以及 Proxy。Proxy 用来授权访问 forum:注册用户和游客权限不同,而且权限贯穿整个系统,用 proxy 类似网关。
六、Java 宠物店的 CatalogDAOFactory
public class CatalogDAOFactory {
/**
* 本方法指定一个特别的子类来实现 DAO 模式。
* 具体子类定义是在 J2EE 的部署描述器中。
*/
public static CatalogDAO getDAO() throws CatalogDAOSysException {
CatalogDAO catDao = null;
try {
InitialContext ic = new InitialContext();
// 动态装入 CATALOG_DAO_CLASS
// 可以定义自己的 CATALOG_DAO_CLASS,从而在无需变更太多代码
// 的前提下,完成系统的巨大变更。
String className = (String) ic.lookup(JNDINames.CATALOG_DAO_CLASS);
catDao = (CatalogDAO) Class.forName(className).newInstance();
} catch (NamingException ne) {
throw new CatalogDAOSysException(
"CatalogDAOFactory.getDAO: NamingException while getting DAO type : \n"
+ ne.getMessage());
} catch (Exception se) {
throw new CatalogDAOSysException(
"CatalogDAOFactory.getDAO: Exception while getting DAO type : \n"
+ se.getMessage());
}
return catDao;
}
}
CatalogDAOFactory 是典型工厂方法。catDao 通过动态类装入器拿到具体实现子类,Java 宠物店里用来操作 catalog 数据库。用户可按数据库类型定制子类,把类名交给 CATALOG_DAO_CLASS 即可。
由此可见,工厂方法为系统提供了灵活的动态扩展:只要更换具体工厂方法,系统其他地方无需改动,功能就可以改头换面。
一句话总结:工厂把 new 和初始化从调用方拆走:一种产品用工厂方法(常配反射),一组相关产品用抽象工厂;换实现只改工厂或配置,不改业务代码。