《Effective Java》中第一条目提出的使用静态工厂方法代替构造器的建议,分析静态工厂方法的优点、常见应用场景,以及其命名惯例,帮助开发者更好地理解并应用这一设计模式。

什么是构造器?

相信看到这篇博客的读者已经掌握了java的一些基础用法,那么一定对构造器(构造方法)并不陌生了。下面使用一个简单的代码栗子稍微回顾一下构造器吧~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class User {

private String username;
private String password;

/** 构造器方式创建新对象
* @deprecated 采用静态工厂方式代替构造器
* @param username 用户名
* @param password 密码
* 使用方法 User user = new User"(username,password)
*/
public User(String username, String password) {
this.username = username;
this.password = password;
}
}

这里使用 public + 类名的形式就是构造器,我们可以通过构造器来创建一个对象

什么是静态工厂方法?

static 这个修饰词在编写代码的过程中经常出现,在大家写 public static void main(String[] args) 这个函数入口的时候就有写过 static ,这个修饰词表示方法或变量属于类本身,而不是某个具体对象。static 并不会让方法提前执行,而是表示这个方法可以通过类名直接调用。

我们用一个栗子来看看如何使用静态工厂方法代替构造器吧~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public User{    
/** 私有构造器,避免外部直接创建对象
* @param username 用户名
* @param password 密码
*/
private User(String username, String password) {
this.username = username;
this.password = password;
}

/** 静态工厂方式创建新对象
* @param username 用户名
* @param password 密码
* @return User对象
*/
public static User of(String username, String password) {
return new User(username, password);
}
}

我们把构造器私有化,不允许外部直接创建对象,而是用静态工厂 of 方法创建对象。这种方法非常常见,我们可以参看java中Boolean类型下的valueOf方法,使用的就是静态工厂方法。

1
2
3
4
@IntrinsicCandidate
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}

使用静态工厂方法有什么好处?

在《Effective Java》一文中提出了五个使用静态工厂方法代替构造器的好处,接下来我们一点一点进行分析.

1 静态工厂方法拥有名称

这是指在使用静态工厂方法时,开发者可以通过自定义方法名赋予其特定的含义,从而使代码更加直观和可读。例如,常见的命名方式如 from() , of() , getInstance() 等,能够很好地传达对象创建的意图。通过这样的命名,调用方可以更清晰地理解工厂方法的目的,例如用于转换、构建或初始化对象。当构造函数的参数过多或意义不够清晰时,静态工厂方法能够通过良好的命名策略提供更具可读性和表达力的替代方案。

2 调用时无需创建新对象

这是指我们使用的静态工厂方法通过内部缓存机制,避免每次调用时都创建新对象。与直接调用构造函数不同,静态工厂方法可以控制对象的创建逻辑,例如在方法内部缓存已经创建的实例并在后续调用中直接返回。这种做法尤其适用于创建代价较高或频繁使用的对象,能够显著提升性能和资源利用效率。

这里需要提到两个设计模式:享元模式单例模式 。这里的思想和享元模式所使用的技术类似,享元模式的意图是不可变对象的复用;单例模式是这个类仅存在一个实例,对于重复多次的调用静态工厂可以返回同一个对象,这就能将一个类设计为一个实例受控类,进而实现单例设计模式。

3 可以返回声明的返回类型的任何子类型的对象

这使得在选择返回对象的类时,就有了很大的灵活性。静态工厂方法并不局限于返回某个具体类的实例,而是可以返回该类的任何子类对象。这一特性使得在设计API时更具扩展性和可维护性。例如,在接口或抽象类作为返回类型的情况下,静态工厂方法可以根据具体场景返回不同实现类的实例,而调用方只需要依赖于返回类型即可,不必关心实际返回的是哪个子类。

这种灵活性可以让开发者在不改变外部接口或调用代码的前提下,通过调整工厂方法的实现来引入新的子类或不同的实现方式。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public interface Vehicle {
void drive();
}

public class Car implements Vehicle {
@Override
public void drive() {
System.out.println("Driving a car");
}
}

public class Truck implements Vehicle {
@Override
public void drive() {
System.out.println("Driving a truck");
}
}

public class VehicleFactory {
public static Vehicle getVehicle(String type) {
if (type.equals("car")) {
return new Car();
} else if (type.equals("truck")) {
return new Truck();
}
throw new IllegalArgumentException("Unknown vehicle type");
}
}

在这种情况下,VehicleFactory的静态工厂方法getVehicle()可以返回CarTruck,而返回类型是通用的Vehicle接口。调用方不需要知道具体的实现,只需依赖于Vehicle接口来进行操作。这种设计允许将来在不改变现有代码的情况下添加新的子类型,实现了高度的灵活性和可扩展性。

4 在方法每次被调用时,所返回对象的类可以随输入参数的不同而改变

这个优点显而易见,用上文举栗的User类做说明:假设我们有一个UserFactory,可以根据不同的参数创建不同级别的用户,例如普通用户、管理员等。这意味着同一个工厂方法在接收不同的输入时,会返回不同的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class User {
private String name;
private String role;

public User(String name, String role) {
this.name = name;
this.role = role;
}

public String getRole() {
return role;
}
}

public class AdminUser extends User {
public AdminUser(String name) {
super(name, "Admin");
}
}

public class RegularUser extends User {
public RegularUser(String name) {
super(name, "Regular");
}
}

public class UserFactory {
public static User createUser(String name, String role) {
if ("admin".equals(role)) {
return new AdminUser(name);
} else {
return new RegularUser(name);
}
}
}

在这个例子中,UserFactory的静态工厂方法createUser()根据传入的role参数,返回不同的子类对象——如果传入的是“admin”,返回的是AdminUser;如果是其他角色,则返回RegularUser。这样一来,调用方可以使用同一个工厂方法创建不同类型的用户,而不需要知道这些具体的实现类。这种灵活性不仅简化了调用方的代码,还为将来增加新的用户类型提供了扩展的可能性。当你使用的Spring Boot或模块化开发时,这种方法也能很好地适应复杂的业务场景。例如,你可以根据用户的角色、权限等信息灵活返回不同的用户对象,从而在系统中实现更高的灵活性和可维护性。

5 所返回对象的类并不一定要存在

静态工厂方法返回的对象的具体类型不需要是代码中明确存在的类,也就是说,静态工厂方法并不需要直接返回某个现有类的实例。它可以返回接口类型、抽象类,甚至是代理对象等,从而使得返回的对象实现某种行为,而不一定是实际存在的某个类。静态工厂方法的优势之一在于,它可以通过一定的逻辑,返回不同的实现类的对象,这些实现类可能是在运行时动态生成的,或者是某些框架(例如 Spring 或动态代理机制)通过反射等手段在运行时创建的。

这种灵活的静态工厂方法构成了 服务提供者框架 的基础。

什么是服务提供者框架?

服务提供者框架的核心思想是 面向接口编程 ,通过接口或抽象类定义服务,然后由多个服务提供者(服务的具体实现类)来提供不同的实现。服务使用者不需要知道具体的实现,而是通过某种机制选择并使用其中一个服务提供者。JDBC(Java Database Connectivity)是一个典型的服务提供者框架。它采用了面向接口编程的思想,允许在运行时动态加载数据库驱动,并通过统一的接口与不同的数据库进行交互,而不需要在编译时绑定具体的数据库实现。

静态工厂方法在服务提供者框架中的角色

  1. 解耦实现与使用
    静态工厂方法通过返回接口类型而不是具体实现类,完全隐藏了实现细节。服务使用者只知道通过工厂方法获取某个接口的实例,却不知道也不需要知道具体的类是什么。这正符合服务提供者框架的思想,即服务使用者不关心实现,只关心接口。
  2. 动态选择实现
    在服务提供者框架中,服务可能有多个提供者,工厂方法可以在运行时根据不同的条件动态选择具体的服务实现。例如,基于配置文件、环境变量、输入参数等,静态工厂方法可以返回不同的服务实现。这种动态选择机制通常通过静态工厂方法来实现。
  3. 懒加载和性能优化
    静态工厂方法可以实现延迟加载,即在需要时才加载和初始化具体的服务实现。这在服务提供者框架中非常有用,特别是当实现类的加载和初始化开销较大时。工厂方法可以缓存已经创建的实例,避免不必要的重复创建。

那么这种方法是否存在缺点呢?

当然,没有完美的设计,只提供静态工厂方法也存在着一些短板。

1 如果没有public或protect的构造器,就无法为这样的类创建子类

2 程序员很难找到它们

静态工厂方法通用命名惯例

of()valueOf():通常用于轻量、简单的对象创建。

getInstance():通常用于单例模式或共享实例的场景。

newInstance():通常用于每次都创建新对象的场景。

create():表明创建并返回一个新对象。

在《Effective Java》中有提到更多的命名惯例:

  • from —— 一个类型转换方法,接受一个参数并返回该类型的一个对应的实例
  • of —— 一个聚合方法,接受多个参数并返回该类型的一个包含这些参数的实例
  • valueOf —— from和of 的代替方案
  • instance / getInstance —— 根据参数返回实例,每次返回的实例未必有相同的值
  • create / newInstance —— 该方法确保每次调用都返回一个新的实例
  • getType —— 在该工厂方法处于不同的类中使用
  • newType —— 当该工厂方法处于不同的类中时使用
  • type —— getType和newTypede 简洁代替版本