与 GoF 工厂方法的区别
静态工厂方法(Static Factory Method)不是 GoF 23 种设计模式之一,容易和 GoF 的"工厂方法模式(Factory Method Pattern)“混淆,两者本质不同:
| GoF 工厂方法模式 | 静态工厂方法 | |
|---|---|---|
| 出处 | GoF《设计模式》 | Effective Java Item 1(Joshua Bloch) |
| 本质 | 定义创建对象的接口,由子类决定实例化哪个类 | 用静态方法代替构造器来创建对象 |
| 是否 GoF 模式 | ✅ 是 | ❌ 不是,是编程惯用法(idiom) |
静态工厂方法是 Joshua Bloch 在《Effective Java》Item 1 中推荐的编程惯例:用命名的静态方法代替构造函数。
基本概念
创建类的实例的最常见方式是用 new 调用构造方法。每执行一条 new 语句,都会在堆区产生一个新对象。假如类需要封装创建实例的细节、或控制实例数量,可以改用静态工厂方法。
典型例子:Class 实例由 JVM 在加载类时自动创建,程序无法用 new 创建 java.lang.Class 的实例(没有 public 构造方法)。为此,Class 提供了静态工厂方法:
Class c = Class.forName("Sample"); // 返回代表 Sample 类的实例
相比构造方法的优势
1. 方法名可以携带语义
构造方法名必须与类名相同,所有重载版本名字一样,难以区分。静态工厂方法可以任意命名,提高可读性:
public class Gender {
private String description;
private static final Gender female = new Gender("女");
private static final Gender male = new Gender("男");
private Gender(String description) { this.description = description; }
public static Gender getFemale() { return female; }
public static Gender getMale() { return male; }
public String getDescription() { return description; }
}
常见命名约定:valueOf(类型转换语义)、getInstance(获取实例语义)。
Integer a = Integer.valueOf(100);
Calendar c = Calendar.getInstance(Locale.CHINA);
2. 不必每次创建新对象
new 每次都产生新对象,而静态工厂方法是否创建新对象完全取决于实现。这一特点适合:
- 单例类:只有唯一实例
- 枚举类:实例数量有限
- 带缓存的类:复用已创建的实例
- 不可变类:实例一旦创建属性不再变化
3. 可以返回子类型
new 只能创建当前类的实例,静态工厂方法可以返回子类的实例,有助于构建松耦合的接口。
单例的两种实现方式
方式一:public static final 字段
public class GlobalConfig {
public static final GlobalConfig INSTANCE = new GlobalConfig();
private GlobalConfig() { ... }
}
三个关键字各司其职,缺一不可:
static:字段属于类本身而非某个实例,JVM 加载类时只初始化一次,整个程序生命周期内只有这一份。final:引用不可被重新赋值,防止外部执行GlobalConfig.INSTANCE = null或替换成新对象。private构造方法:外部无法new出新实例,只能通过INSTANCE访问。
| 缺少什么 | 后果 |
|---|---|
缺 static |
每个实例都有自己的 INSTANCE 字段,没有"全局唯一"的语义 |
缺 final |
外部可以执行 GlobalConfig.INSTANCE = null 或替换成新对象 |
构造方法不是 private |
外部可以直接 new GlobalConfig() 绕过 INSTANCE |
简洁直观,成员声明直接表明是单例。
方式二:静态工厂方法(推荐)
public class GlobalConfig {
private static final GlobalConfig INSTANCE = new GlobalConfig();
private GlobalConfig() { ... }
public static GlobalConfig getInstance() { return INSTANCE; }
}
灵活性更高:在不改变接口的前提下可以修改内部实现,例如改为每个线程一个实例:
public class GlobalConfig {
private static final ThreadLocal<GlobalConfig> threadConfig = new ThreadLocal<>();
private GlobalConfig() { ... }
public static GlobalConfig getInstance() {
GlobalConfig config = threadConfig.get();
if (config == null) {
config = new GlobalConfig();
threadConfig.set(config);
}
return config;
}
}
局限性
由于使用静态方法,静态工厂方法有一定局限性(如无法继承)。Java 的反射机制可以在一定程度上弥补这些不足,结合其他模式可以达到更好的构建效果。