静态工厂方法代替构造器是《Effective Java》(第3版) 中非常重要的一条建议,** 避免直接使用构造器,优先考虑静态工厂方法**。这是编写高质量、易维护、灵活代码的一个关键技巧。
静态工厂方法是什么?
静态工厂方法是通过 类的静态方法 来创建实例,而不是直接通过构造器创建对象。
示例说明传统构造器与静态工厂方法的对比:
1. 使用构造器
public class User {
private final String name;
private final int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
调用方式 → 标准构造器:
User user = new User("Alice", 30);
2. 使用静态工厂方法
public class User {
private final String name;
private final int age;
// 私有构造器,限制直接创建对象
private User(String name, int age) {
this.name = name;
this.age = age;
}
// 静态工厂方法用于创建实例
public static User of(String name, int age) {
return new User(name, age);
}
}
调用方式 → 静态方法:
User user = User.of("Alice", 30);
与构造器创建看起来相似,但静态工厂方法有很多优点。
静态工厂方法的优势
书中强调:与直接使用构造器相比,静态工厂方法提供了更大的灵活性和可维护性。以下是具体优点:
1. 可以为方法命名,提高代码可读性
- 构造器只能通过类名进行标识,但静态工厂方法可以取一个有意义的名字,描述返回对象的意图。
比较两种代码:
BigInteger(int bitLength, int certainty, Random rnd); // 构造器
BigInteger.probablePrime(int bitLength, Random rnd); // 静态工厂方法
probablePrime 作为名字明确表达了工厂方法的真正意图,因此更加直观。而构造器 BigInteger(int bitLength, ...) 则没有这样的语义信息。
2. 可以控制实例的数量,返回单例或缓存实例
- 通过静态工厂方法,有机会缓存已经创建的实例,比如返回 单例模式对象 或 复用对象。
单例示例:
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() { } // 私有构造器
public static Singleton getInstance() {
return INSTANCE;
}
}
调用:
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // true,始终返回同一个实例
此外,还可以实现对象池(缓存):
public class User {
private static final Map<String, User> CACHE = new HashMap<>();
private final String name;
private User(String name) {
this.name = name;
}
public static User of(String name) {
return CACHE.computeIfAbsent(name, User::new);
}
}
3. 可以返回子类或特定实现对象
- 静态工厂方法不需要返回该类的对象,可以返回任意子类的实例,这样能有效隐藏实现细节。
示例:
public interface Car {
void drive();
}
class ElectricCar implements Car {
@Override
public void drive() {
System.out.println("Electric car driving...");
}
}
class GasCar implements Car {
@Override
public void drive() {
System.out.println("Gas car driving...");
}
}
class CarFactory {
public static Car getCar(String type) {
return switch (type) {
case "electric" -> new ElectricCar();
case "gas" -> new GasCar();
default -> throw new IllegalArgumentException("Unknown car type");
};
}
}
调用:
Car car = CarFactory.getCar("electric");
car.drive(); // Electric car driving...
优点:调用者不需要知道具体类型,只关注接口或父类。可以轻松复用接口实现,且隐藏子类,增强了灵活性。
4. 延迟实例化,提高性能
- 静态工厂方法可以根据需要控制实例化。
例如,Boolean.valueOf() 方法返回了 Boolean.TRUE 或 Boolean.FALSE,确保所有使用者共享同一实例,而不是每次创建新的对象:
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
这种直接复用极大地提高了性能。
5. 灵活定制返回结果类型
- 静态工厂方法可以根据参数灵活地返回不同的类型实例,这种灵活性是构造器无法实现的。
例如,返回不可变集合:
List<String> list = List.of("A", "B", "C");
Set<Integer> set = Set.of(1, 2, 3);
Java 标准库中的例子:
Collections.unmodifiableList(...)返回的是不可变视图对象,防止对原集合的修改。
6. 适合参数类型复杂的情况
- 使用静态工厂方法时,我们可以通过不同的方法名称将参数组合简化,不需要多个类似重载的构造器。
传统构造器的缺点:
public class User {
private String name;
private int age;
private boolean isAdmin;
public User(String name) { ... }
public User(String name, int age) { ... }
public User(String name, int age, boolean isAdmin) { ... }
}
重载构造器太多,容易混淆。
替代方案:
public class User {
private String name;
private int age;
private boolean isAdmin;
private User(String name, int age, boolean isAdmin) {
this.name = name;
this.age = age;
this.isAdmin = isAdmin;
}
public static User ofName(String name) {
return new User(name, 0, false);
}
public static User ofNameAndAge(String name, int age) {
return new User(name, age, false);
}
public static User admin(String name, int age) {
return new User(name, age, true);
}
}
静态工厂方法的缺点
当然,静态工厂方法也有一些不足之处:
-
难以发现:相比构造器,静态工厂方法不能靠 “new” 关键字直观发现,需通过文档了解其用法。
- 解决方案:为静态工厂方法起一个清晰的名字,让方法意图更明显。
-
无法被子类继承:因为静态方法不能被子类重写,可能会影响扩展性。
- 解决方案:将类设计为
final或提供更高层次的抽象(接口 + 工厂类)。
- 解决方案:将类设计为
在软件开发中,是否将类设计为 final 或通过更高层次的抽象解决扩展问题,往往取决于具体需求和设计情境。这两种方法各有适用场景,下面用一个更具体的例子来讲解这两种方式的使用:
方案 1:将类设计为 final
意图:
通过将类设计为 final,防止子类继承或改写它的行为。这种做法适用于工具类、不可变类或那些不希望被继承和扩展的类。
示例代码:
public final class PaymentService {
private final String paymentMethod;
// 私有构造器
private PaymentService(String paymentMethod) {
this.paymentMethod = paymentMethod;
}
// 静态工厂方法:创建具体支付服务
public static PaymentService createAliPayService() {
return new PaymentService("AliPay");
}
public static PaymentService createWeChatPayService() {
return new PaymentService("WeChatPay");
}
public void processPayment(double amount) {
System.out.println("Processing " + paymentMethod + " payment of $" + amount);
}
}
使用方式:
PaymentService alipay = PaymentService.createAliPayService();
alipay.processPayment(100.00);
PaymentService weChatPay = PaymentService.createWeChatPayService();
weChatPay.processPayment(200.00);
特点:
final修饰:类不可继承,确保其行为是固定的(如安全性保证、防止修改,特别适合工具类)。- 静态工厂方法:屏蔽了具体构造过程,调用者只能通过方法名选择对象类型(比如返回支付宝或微信支付实例)。
- 局限性:
- 一旦定义了
final,就无法扩展。如果需要追加新的支付方式(例如 PayPal),需要在原类中添加新的静态方法并修改类代码。
- 一旦定义了
方案 2:接口 + 工厂类
意图:
提供一个更高层次的抽象,通过接口定义行为,由工厂类动态决定返回具体的实现。这种设计更灵活,易于维护和扩展。
示例代码:
1. 定义接口:
public interface PaymentService {
void processPayment(double amount);
}
2. 提供具体实现类:
public class AliPayService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("Processing AliPay payment of $" + amount);
}
}
public class WeChatPayService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("Processing WeChatPay payment of $" + amount);
}
}
public class PayPalService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment of $" + amount);
}
}
3. 创建工厂类:
public class PaymentServiceFactory {
public static PaymentService getPaymentService(String type) {
return switch (type.toLowerCase()) {
case "alipay" -> new AliPayService();
case "wechatpay" -> new WeChatPayService();
case "paypal" -> new PayPalService();
default -> throw new IllegalArgumentException("Unknown payment type: " + type);
};
}
}
使用方式:
PaymentService alipay = PaymentServiceFactory.getPaymentService("alipay");
alipay.processPayment(100.00);
PaymentService weChatPay = PaymentServiceFactory.getPaymentService("wechatpay");
weChatPay.processPayment(200.00);
PaymentService paypal = PaymentServiceFactory.getPaymentService("paypal");
paypal.processPayment(300.00);
特点:
-
接口抽象:调用方无需关心具体类的实现(
AliPayService、WeChatPayService等),只与接口PaymentService交互。这使得代码更具扩展性。 -
工厂动态实例化:工厂类
PaymentServiceFactory通过传入的参数,动态决定返回哪个具体实现。 -
易扩展:添加新的支付方式,只需新增实现类和修改工厂逻辑(而不需要修改已有类的代码):
新增一个支付方式(例如
StripeService):public class StripeService implements PaymentService { @Override public void processPayment(double amount) { System.out.println("Processing Stripe payment of $" + amount); } } // 修改工厂类: public static PaymentService getPaymentService(String type) { return switch (type.toLowerCase()) { case "alipay" -> new AliPayService(); case "wechatpay" -> new WeChatPayService(); case "paypal" -> new PayPalService(); case "stripe" -> new StripeService(); default -> throw new IllegalArgumentException("Unknown payment type: " + type); }; }
两种方案的对比
| 特点 | final 类方案 | 接口 + 工厂类方案 |
|---|---|---|
| 扩展性 | 不可扩展,新增支付方式需要修改原类 | 非常灵活,添加新实现无需修改已有代码 |
| 适用场景 | 行为固定,稳定,不希望子类扩展 | 行为多样,依赖多种实现,鼓励接口分层设计 |
| 复杂性 | 较低,适合简单场景实现 | 较高,对接更多实现类和分层需求 |
| 设计模式 | 静态工厂 | 工厂模式 |
| 子类限制 | 不允许子类继承 | 调用方只关心接口,无需了解具体的实现类 |
总结和应用建议
-
使用
final类:- 如果当前类是小型工具类(如
StringUtils、Math),或对外暴露的是一个行为固定、简单且无需扩展的类,使用final关键字能防止滥用继承,同时提升安全性。 - 适合进行稳定行为封装,如单例模式或者不可变对象。
- 如果当前类是小型工具类(如
-
使用接口 + 工厂类:
- 若系统需求复杂且容易变化(如支持多种支付方式、插件化架构等),推荐使用接口加工厂类的模式。利用接口定义行为、工厂管理创建过程,实现解耦和扩展性。
- 遵循 “面向接口编程” 的原则,便于后期维护和扩展。

被折叠的 条评论
为什么被折叠?



