第1项:考虑静态工厂方法而不是构造函数

  类容许客户端获取实例的传统方法是提供公共构造器。还有一种技术应该是每一个程序员的工具箱的一部分。一个类能够提供一个公共静态工厂方法,它仅仅是一个返回类实例的静态方法。下面是布尔(布尔型的盒装原语类)的一个简单示例。这个方法将一个布尔原始值转换成布尔对象引用:java

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

  注意: 静态工厂方法与设计模式 [Gamma95] 的工厂方法模式不一样,在这个Item中描述的静态工厂方法并不等同于设计模式中的工厂方法模式。程序员

  一个类能够为其客户提供静态工厂方法,而不是公共构造函数。提供静态工厂方法而不是公共构造函数既有优势也有缺点。数据库

  静态工厂方法的一个优势是,它们是有名称的,而构造函数的名称都是同样的。 若是构造函数的参数自己不能描述返回的对象,那么使用一个精心命名的静态工厂更容易使用,而且生成的客户端代码更容易阅读。例如BigInteger的一个构造函数:BigInteger(int, int, Random),这个构造函数返回一个BigInteger有多是一个质数,使用一个精心命名的静态工厂方法会更容易描述该方法返回的BigInteger类型,好比BigInteger.probablePrime(这是在Java 4 中添加的)。编程

  一个类只能有一个带有给定签名的构造函数。程序员能够经过提供两个构造函数来绕过这个限制,这些构造函数的参数列表只在参数类型的顺序上有所不一样。这是个至关坏的主意。这样使用这个API的用户永远没法记住应该使用哪一个构造函数,而且最终会调用错误的构造函数。使用这些构造函数的人在不阅读的引用类文档的状况下是不知道代码是干什么的。设计模式

  由于构造函数有名称,因此就不会有这个限制,在一个类须要使用多个签名、多个构造函数的状况下,用静态工厂方法代替构造函数,并仔细选择方法的名称就能够突出构造函数之间的差别了。数组

  静态工厂方法的第二个优势是,与构造函数不一样,它们不须要在每次被调用时建立一个新对象。 这容许不可变类(第17项)使用预先构造的实例,或者在构建时缓存实例,并重复分发它们,以免建立没必要要的重复对象。Boolean.valueOf(boolean)方法使用了这种方式:它从不建立对象。这种技术相似于享元模式[Gamma95]。若是常常请求等效对象,特别是当它们的建立成本很高时,它能够极大地提升性能。缓存

  静态工厂方法从重复调用中返回相同的对象的能力容许类在任什么时候候保持对实例的严格控制。这样作的类被认为是实例控制的。编写实例控制类的缘由有几个。实例控制容许一个类保证它是一个单例(第3项)或非实例化的(第4项),而且它容许一个不可值的类(第17项)来保证没有两个相等的实例存在:当且仅当a==b成立时,a.equals(b)返回true,这是享元模式 [Gamma95] 的基础,枚举类型就提供了这种保证。框架

  静态工厂方法的第三个优势是,与构造函数不一样,它们能够的对象能够是返回类型的任何子类的实例对象。 这使在选择返回的对象的类时具备很大的灵活性。dom

  这种灵活性的一个应用是,API能够返回对象,同时又不会使对象的类编程公有的,以这种方式隐藏实现类会使API变得很是简洁。这种技术适用于基于接口的框架(interface-based frameworks,见第20项),由于在这种框架中,接口为静态工厂方法提供了天然返回类型。ide

  在Java 8 以前,接口不能有静态方法。按照惯例,名为Type的接口的静态工厂方法被放置在一个不可实例化的名为Types的配套类(noninstantiable companion class)(第4项)中。例如,Java Collections Framework有45个便利实现,分别提供了不可修改的集合、同步集合等等。几乎全部这些实现都经过静态工厂方法在一个不可实例化的类(java.util.Collections)中导出。全部返回对象的类都是非公有的。

  如今的Collections Framework API比导出的45个独立的公有类的那种实现方式要小得多,每种便利的实现都对应一个类。这不只仅减小了API的数量,还包括概念上的权重:程序猿必须掌握的概念的数量和难度,以便使用API。程序猿知道返回的对象正好有其接口指定的API,所以不须要为实现类去阅读额外的类文档。此外,这种工厂方法要求客户端经过接口而不是实现类来引用返回的对象,这一般是很好的实践方式(第64项)。

  从Java 8开始,消除了接口不能包含静态方法的限制,所以一般没有理由为接口提供不可实例化的伴随类。许多公共静态成员应该放在接口自己中。但请注意,可能仍有必要将大量实现代码放在这些静态方法后面的单独的包私有类中。这是由于Java 8要求接口的全部静态成员都是公共的。 Java 9容许私有静态方法,但静态字段和静态成员类的属性依然是要求是公共的。

  静态工厂方法的第四个优势是,静态工厂方法所返回的对象的类能够随着每次调用而变化,这取决于静态工厂方法的参数值。 只要返回的类型是声明的类的子类都是容许的。返回对象的类也可能随着发行版本的不一样而不一样。

  在EnumSet类(第36项)中有非公有的构造方法,只有静态工厂方法。在 OpenJDK 实现中的,它们返回两个子类之一的实例,具体取决于基础枚举类型的大小: 若是它有64个或更少的元素,就像大多数枚举类型所作的那样,静态工厂返回一个RegularEnumSet实例, 它由单个long的支持;若是枚举类型有65个或更多元素,则工厂将返回一个由长数组支持的JumboEnumSet实例。

  这两个实现类的存在对于客户端是不可见的。 若是 RegularEnumSet 再也不为小枚举类型提供性能优点能够从将来版本中删除,没有任何不良影响。 一样,将来若是证实有利于性能,则能够添加EnumSet的第三或第四个实现。客户即不知道也不关心他们从工厂中获取的对象的类型,他们只关心它是EnumSet的一些子类。

  静态工厂方法的第五个优势是,返回的对象所属的类,在编写包含该静态工厂方法的类时能够没必要存在。 这种灵活的静态工厂方法构成了服务提供者框架(Service Provider Framework)的基础,例如JDBC(Java数据库链接,Java Database Connectivity)API。服务提供者框架是提供者实现服务的系统,系统使实现可用于客户端,将客户端与实现分离【微服务】。

  服务提供者框架中有三个基本组件:服务接口【提供者】,表明一个实现;提供者注册API【注册中心】,提供者用于注册实现; 以及服务访问API【消费者】,客户端使用它来获取服务的实例。 服务访问API能够容许客户端指定用于选择实现的标准。 若是没有这样的标准,API将返回默认实现的实例,或容许客户端循环遍历全部可用的实现。 服务访问API是灵活的静态工厂,它构成了服务提供者框架的基础。

  服务提供者框架的可选第四个组件是服务提供者接口,它描述了生成服务接口实例的工厂对象。 在缺乏服务提供者接口的状况下,必须反复实例化实现(第65项)。 对于JDBC,Connection扮演服务接口的一部分,DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API,Driver是服务提供者接口。

  服务提供者框架模式有许多变体。 例如,服务访问API能够向客户端返回比提供者提供的服务接口更丰富的服务接口。 这是桥接模式 [Gamma95] 。 依赖注入框架(第5项)可视为强大的服务提供者。 从Java 6开始,该平台包含一个通用服务提供程序框架java.util.ServiceLoader,所以您不须要(一般不该该)本身编写(第59项)。 JDBC不使用ServiceLoader,由于前者早于后者。

  静态工厂方法的主要限制在于,类若是不含公有的或者受保护的构造器,就不能被子类化。 例如:不可能将Collections Framework中的任何方便的实现类子类化。可是这也许会塞翁失马,由于它鼓励程序猿使用组合,而不是继承(第18项),而且要求必须是不可变的(第17项)。

  静态工厂方法的第二个缺点是程序员很难找到它们。 它们不像构造函数那样在API文档中脱颖而出,所以很难弄清楚如何实例化提供静态工厂方法而不是构造函数的类。 Javadoc工具备一天可能会引发对静态工厂方法的注意。 在此期间,您能够经过引发对类或接口文档中的静态工厂的注意并遵照常见的命名约定来减小此问题。 如下是静态工厂方法的一些经常使用名称。 这份清单远非详尽无遗:

  • from:一种类型转换方法,它接受单个参数并返回此类型的相应实例,例如:Date d = Date.from(instant);
  • of:一种聚合方法,它接受多个参数并返回包含它们的此类型的实例,例如:Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf:一个更详细的替代方案,例如:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • instance or getInstance:返回由其参数(若是有)描述的实例,但不能说它具备相同的值,例如:StackWalker luke = StackWalker.getInstance(options);
  • create or newInstance:与instance或getInstance相似,不一样之处在于该方法保证每一个调用都返回一个新实例,例如:Object newArray = Array.newInstance(classObject, arrayLen);
  • getType:与getInstance相似,是在工厂方法位于不一样的类中时使用它。 Type指的是工厂方法返回的对象类型,例如:FileStore fs = Files.getFileStore(path);
  • newType:与newInstance相似,是在工厂方法位于不一样的类中的时候使用。Type指的是工厂方法方位的对象类型,例如:BufferedReader br = Files.newBufferedReader(path);
  • type:获取Type和new Type一个简明替代的方法,好比:List<Complaint> litany = Collections.list(legacyLitany);

  总之,静态工厂方法和公共构造函数都有它们的用途,理解它们的相对优势是值得的。 一般静态工厂是优选的,不要在第一反应就是使用构造函数,应当先考虑使用静态工厂方法。

我的公众号关注公众号获取同步更新

相关文章
相关标签/搜索