序言
最近需求写完, 测试也测完发布到rc环境后, 有一个涉及第三方接口的地方需要做一点调整, 于是就在等待第三方回应的闲暇之余拿起来几年前买的一本好书 << Effective Java>> 第三版来重读下部分内容, 今天分享的就是第二章的第一条: 用静态工厂方法代替构造器.
首先我打开自己写的代码, 尤其是一些业务对象, 大多数的类都是这么定义的:
java
@Data
public class TestModel {
private Long xx;
private String yy;
使用的时候就是 new TestModel(xx,yy) 这样, 这是非常合乎常理的一种使用方式. 但是这里本书就提到了, 我们程序员的工具箱里应该还有一个创建对象实例的方法, 就是让类提供一个或多个公有的静态工厂方法(static factory method), 而且在Java源码里面就有很多地方用到并且我们会经常用到, 比如: BigDecimal.valueOf, Date.from, LocalDate.of...
静态工厂方法的优势
在有了构造方法后为什么还要这么建议呢? 在书里给出了5条理由:
- 有名字
- 有需求的时候, 不必每次都创建一个新对象
- 返回对象类型可以是返回类型的任何子类型的对象
- 返回对象的类型可以根据参数值的变化而变化
- 返回对象的具体类型可以不存在
让我来逐个演示下这五个优势的具体体现:
- 有名字
java
public static TestModel withIdAndName(Long id, String name) {
TestModel testModel = new TestModel();
testModel.setId(id);
testModel.setName(name);
return testModel;
}
这种随便自定义名称的方法肯定比冰冷的构造器更容易理解.
- 有需求的时候, 不必每次都创建一个新对象
java
public static TestModel commonInstance() {
return EmptyModel.INSTANCE;
}
该方法每次放回的都是同一个对象, 我这里实现的方法是通过静态内部类的方式类实现的, 也可以通过其他单例模式的实现来实现该需求. 这里需要注意的是, 由于是同一个对象, 需要考虑引用传递的问题.
- 返回对象类型可以是返回类型的任何子类型的对象
csharp
public static TestModel modelA() {
return new ModelA();
}
很明显这里返回的是TestModel的子类 ModelA 的对象实例.
- 返回对象的类型可以根据参数值的变化而变化
typescript
public <T extends TestModel> TestModel typeModel(T type) {
if(type instanceof ModelA a) {
return a;
}else if(type instanceof ModelB b) {
return b;
}
return null;
}
这个方法返回对象的类型就取决于参数type的具体类型.
- 返回对象的具体类型可以不存在
csharp
public static PostgresqlConnect connect() {
return ServiceLoader.load(PostgresqlConnect.class).findFirst().orElse(null);
}
不知道这里我理解的对不对, 这里方法的返回值是一个接口, 具体的实现类在方法调用的时候去指定的位置加载, Java的SPI机制应该也是基于这个基础.
静态工厂方法的劣势
说完这么多优点, 还有两个缺点也要交待一下:
- 类如果不含有公有的或者受保护的构造器, 就不能被子类化
- 程序员很难去发现他们. 通常在API文档里不会像描述构造器一样去描述静态构造方法, 但是可以通过标准的命名习惯可以些微弥补这个缺点, 列举一些在JDK源码中常用的名称: from, of, valueOf, instance/getInstance, create/newInstance, getType, newType, type...
总结
静态工厂方法和公有构造器各有特点, 在写代码的时候切记第一反应是构造器而完全忽略静态工厂.