Java架构师教你写代码(一) - 使用静态工厂方法替代构造器

客户端获得一个类实例的传统方式是调用由类提供的public构造器。但还有一种技术,一个类可以提供public的静态工厂方法,只是一个返回类实例的静态方法。

静态工厂方法与设计模式的工厂方法模式不同。在设计模式中并无直接等价的说法。

2 优点

2.1 实名制

如果构造器的参数本身并不能描述清楚返回的对象,那么具有确切名称的静态工厂则代码可读性更佳!

例如 BigInteger 类的构造器 BigInteger(int, int, Random) 返回值多半是质数,那么最好使用静态工厂方法: BigInteger.probablePrime
Java架构师教你写代码(一) - 使用静态工厂方法替代构造器_第1张图片

一个类只能有一个带给定签名的构造器。可 SE 一般还能通过提供两个构造器来解决,而构造器的参数列表就仅在参数类型的顺序上不同。dirty code!这样的 API,用户永远无法记住该用哪个构造器,并且最终会错误地调用不合适的构造器。不阅读类文档,使用者人根本不知道代码的作用。

而静态工厂方法有确切的名称,所以没这局限。如果一个类就是需要具有相同签名的多个构造器,那么静态工厂方法就很 nice,注意精心的命名来突出它们的区别。

2.2 无需在每次调用时创建新对象

这使得不可变类使用事先构造好的实例,或在构造实例时缓存实例,重复分配以避免创建不必要的重复对象。Boolean.valueOf(boolean) 方法:它从不创建对象。
Boolean类中该方法将 boolean 基本类型值转换为一个 Boolean 对象引用

  • 返回一个Boolean表示指定实例boolean的值。 如果指定的boolean值是true ,则此方法返回Boolean.TRUE ; 如果是false ,这个方法返回Boolean.FALSE 。 如果并不需要一个新的Boolean 实例,该方法一般应优于构造器中使用Boolean(boolean) ,因为此方法可能产生显著的更好的空间和时间性能
    Java架构师教你写代码(一) - 使用静态工厂方法替代构造器_第2张图片

这类似于享元模式。如果经常请求相同对象,特别是创建对象代价高时,可以极大提高性能。

静态工厂方法在重复调用下返回相同对象,这样类能严格控制存在的实例。这样的类称为实例受控的类。编写实例受控的类有几个原因。

  • 允许一个类来保证它是一个单例或不可实例化的。同时,它允许一个不可变的值类保证不存在两个相同的实例:
    a.equals(b) 当且仅当 a==b。这是享元模式的基础。枚举类型提供了这种保证。

2.3 获取返回类型的任何子类的对象

这为选择返回对象的类提供灵活性。

这种灵活性的一个应用是 API 可以在public其类的情况下返回对象。以这种方式隐藏实现类会形成一个非常紧凑的 API。这适用于基于接口的框架,其中接口为静态工厂方法提供了自然的返回类型。

Java 8 前,接口不能有静态方法。按照惯例,一个名为 Type 的接口的静态工厂方法被放在一个名为 Types 的不可实例化的伴生类。例如,Java 的 Collections 框架有 45 个接口实用工具实现,提供了不可修改的集合、同步集合等。几乎所有这些实现都是通过一个非实例化类(java.util.Collections)中的静态工厂方法导出的。返回对象的类都是非public的。

现在的Collections 框架 API 比它导出 45 个独立的public类小得多,每个公共类对应一个方便的实现。减少的不仅仅是 API 的数量,还有概念上的减少:程序员为了使用 API 必须掌握的概念的数量和难度。程序员知道返回的对象是由相关的接口精确指定的,因此不需阅读额外的类文档。
使用这种静态工厂方法需要客户端通过接口而不是实现类引用返回的对象,这通常是很好的做法

Java 8 取消了接口不能包含静态方法的限制,因此通常没有理由为接口提供不可实例化的伴生类。许多公共静态成员应该放在接口本身中,而不是放在类中。但仍有必要将这些静态方法背后的大部分实现代码放到单独的包私有类中。因为 Java 8 要求接口的所有静态成员都是公共的,而 Java 9 允许接口有私有的静态方法,但是静态字段和静态成员类仍然需是public

A fourth advantage of static factories is that the class of the returned object can vary from call to call as a function of the input parameters. Any subtype of the declared return type is permissible. The class of the returned object can also vary from release to release.

2.4 返回对象的类可以随调用的不同而变化

这当然取决于输入参数不同。只要是已声明的返回类型的子类型都是允许的。返回对象的类也可以因版本而异。

  • EnumSet 类没有public构造器
    Java架构师教你写代码(一) - 使用静态工厂方法替代构造器_第3张图片

只有静态工厂。在 OpenJDK 中,它们返回两种子类之一的一个实例,这取决于底层 enum 类型的大小:

  • 如果它有 64 个或更少的元素,就像大多数 enum 类型一样,静态工厂返回一个 long 类型的 RegularEnumSet 实例
  • 如果 enum 类型有 65 个或更多的元素,工厂将返回一个由 long[] 类型的 JumboEnumSet 实例。

客户端看不到这两个实现类的存在。如果 RegularEnumSet 不再为小型 enum 类型提供性能优势,它可能会在未来的版本中被删除,而不会产生任何负面影响。类似地,如果证明 EnumSet 有益于性能,未来的版本可以添加第三或第四个 EnumSet 实现。客户端既不知道也不关心从工厂返回的对象的类;它们只关心它是 EnumSet 的某个子类。

2.5 当编写包含静态工厂方法的类时,返回对象的类可以不存在

这种灵活的静态工厂方法构成了服务提供者框架(Service Provider Framework,SPF)的基础,比如 JDBC API。

SPF系统

多个提供者实现一个服务,该系统使客户端可以使用这些实现,从而将客户端与实现分离。

SPF有三个基本组件

  • 代表实现的服务接口
  • 提供者注册 API,提供者使用它来注册实现
  • 服务访问 API,客户端使用它来获取服务的实例。服务访问 API 允许客户端指定选择实现的条件。在没有这些条件的情况下,API 返回一个默认实现的实例,或者允许客户端循环使用所有可用的实现。服务访问 API 是灵活的静态工厂,它构成了服务提供者框架的基础。

SPF第四个可选组件是服务提供者接口,它描述产生服务接口实例的工厂对象。在没有服务提供者接口的情况下,必须以反射的方式实例化实现。

JDBC案例

  1. Connection扮演服务接口的一部分
  2. DriverManager.registerDriver 是提供者注册 API
  3. DriverManager.getConnection 是服务访问 API
  4. Driver是服务提供者接口

SPF模式有许多变体。例如,服务访问 API 可以向客户端返回比提供者提供的更丰富的服务接口,这就是桥接模式 。依赖注入(DI)框架就可以看成是强大的服务提供者。从Java 6开始,平台就提供了一个通用服务提供者框架 Java.util.ServiceLoader,所以你不需要,通常也不应该自己写。JDBC 不使用 ServiceLoader,因为前者比后者早!

3 缺点

3.1 仅提供静态工厂方法的主要局限是,没有public或protected构造器的类不能被继承

例如,不可能在集合框架中子类化任何便利的实现类。这可能是一种因祸得福的做法,因为它鼓励程序员使用组合而不是继承,这对于不可变类型是必须的。

3.2 程序员很难找到它们

它们在 API 文档中不像构造器吸睛,因此很难弄清楚如何实例化一个只提供静态工厂方法而没有构造器的类。Javadoc 工具总有一天会关注到静态工厂方法。
通过在类或接口文档中多关注静态工厂方法,遵守通用命名约定的方式来减少这个困扰。
下面是一些静态工厂方法的习惯命名。

from,类型转换方法,接收单个参数并返回该类型的相应实例
Java架构师教你写代码(一) - 使用静态工厂方法替代构造器_第4张图片

of,聚合方法,接受多个参数并返回一个实例
Java架构师教你写代码(一) - 使用静态工厂方法替代构造器_第5张图片

valueOf,比 from 和 of 但更繁琐的一种替代方法
Java架构师教你写代码(一) - 使用静态工厂方法替代构造器_第6张图片

instance 或 getInstance,返回一个实例,该实例由其参数(如果有的话)描述,但不和参数具有相同的值

StackWalker luke = StackWalker.getInstance(options);

create 或 newInstance,与 instance 或 getInstance 类似,只是该方法保证每个调用都返回一个新实例

getType,类似于 getInstance,但如果工厂方法位于不同的类中,则使用此方法。其类型是工厂方法返回的对象类型,例如:

FileStore fs = Files.getFileStore(path);

newType,与 newInstance 类似,但是如果工厂方法在不同的类中使用。类型是工厂方法返回的对象类型,例如:
Java架构师教你写代码(一) - 使用静态工厂方法替代构造器_第7张图片

type,一个用来替代 getType 和 newType 的比较简单的方式

List<Complaint> litany = Collections.list(legacyLitany);

Java架构师教你写代码(一) - 使用静态工厂方法替代构造器_第8张图片

总结

静态工厂方法和public构造器 各有千秋,我们需要理解它们各自的优点。通常静态工厂更可取,因此切忌不考虑静态工厂就提供public构造器。

  • 参考
    effective java 第三版英文版

你可能感兴趣的:(编码规范)