Effective Java 用静态工厂方法代替构造器

序言

最近需求写完, 测试也测完发布到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条理由:

  1. 有名字
  2. 有需求的时候, 不必每次都创建一个新对象
  3. 返回对象类型可以是返回类型的任何子类型的对象
  4. 返回对象的类型可以根据参数值的变化而变化
  5. 返回对象的具体类型可以不存在

让我来逐个演示下这五个优势的具体体现:

  1. 有名字
java 复制代码
public static TestModel withIdAndName(Long id, String name) {
    TestModel testModel = new TestModel();
    testModel.setId(id);
    testModel.setName(name);
    return testModel;
}

这种随便自定义名称的方法肯定比冰冷的构造器更容易理解.

  1. 有需求的时候, 不必每次都创建一个新对象
java 复制代码
public static TestModel commonInstance() {
    return EmptyModel.INSTANCE;
}

该方法每次放回的都是同一个对象, 我这里实现的方法是通过静态内部类的方式类实现的, 也可以通过其他单例模式的实现来实现该需求. 这里需要注意的是, 由于是同一个对象, 需要考虑引用传递的问题.

  1. 返回对象类型可以是返回类型的任何子类型的对象
csharp 复制代码
public static TestModel modelA() {
    return new ModelA();
}

很明显这里返回的是TestModel的子类 ModelA 的对象实例.

  1. 返回对象的类型可以根据参数值的变化而变化
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的具体类型.

  1. 返回对象的具体类型可以不存在
csharp 复制代码
public static PostgresqlConnect connect() {
    return ServiceLoader.load(PostgresqlConnect.class).findFirst().orElse(null);
}

不知道这里我理解的对不对, 这里方法的返回值是一个接口, 具体的实现类在方法调用的时候去指定的位置加载, Java的SPI机制应该也是基于这个基础.

静态工厂方法的劣势

说完这么多优点, 还有两个缺点也要交待一下:

  1. 类如果不含有公有的或者受保护的构造器, 就不能被子类化
  2. 程序员很难去发现他们. 通常在API文档里不会像描述构造器一样去描述静态构造方法, 但是可以通过标准的命名习惯可以些微弥补这个缺点, 列举一些在JDK源码中常用的名称: from, of, valueOf, instance/getInstance, create/newInstance, getType, newType, type...

总结

静态工厂方法和公有构造器各有特点, 在写代码的时候切记第一反应是构造器而完全忽略静态工厂.

相关推荐
非优秀程序员3 分钟前
第04章 邮箱大战,163 与 QQ 的暗战|《互联网产品恩仇录》
java·vue.js·架构
知已难寻39 分钟前
Docker 入门:容器技术与 Docker 基础
java·运维·服务器·docker·云原生·容器
文丑颜不良啊3 小时前
LeetCode2481~2500题目解析
java·leetcode
Flynn Liang4 小时前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope
Gopher_HBo4 小时前
Go CPU 剖析的"地基":信号、CAS、写屏障、无锁环形缓冲
后端
panbook4 小时前
从0到1实现一个分布式时序数据库(二):写入成功之后,如何用 WAL 找回数据
后端
Swot4 小时前
上传一张 4MB 图片,我先后撞上了三层请求体限制
后端
聪明的笨猪猪4 小时前
实际工程问题:AI 生成 Spring Cache(@Cacheable / @CachePut / @CacheEvict)缓存注解踩坑
java·经验分享·面试·ai编程
IT_陈寒4 小时前
Vite的HMR把我整不会了:一个生产环境的热更新坑
前端·人工智能·后端
OLRainM5 小时前
实习复盘:大模型网关渠道质量评分实战(二)—— 报错了到底算谁的锅?流式归因与 Token 补账踩过的 8 个真坑
后端