创建销毁对象(第一条:考虑静态工厂方法而非构造器)

第二章.创建销毁对象

这一章主要关注创建和销毁对象:什么时候应该创建它们,该怎样创建它们,什么时候该避免创建它们,怎样避免创建它们。怎样保证他们在合适的情况下被销毁,怎么样来做一些发生在销毁对象之前的清理操作。

 

第一条:考虑静态工厂方法而非构造器

对于一个类而言,允许客户端获取它的实例的传统方法是提供一个公共的构造方法。还有另一个方法应该成为每一个开发人员的开发技巧的一部分。这就是公共的静态工厂方法,只要这个方法是声明为静态的并且能返回这个类的实例。这儿有Boolean(boolean的装箱类型)的一个简单的例子。这个方法把一个boolean的原始类型的值转化成一个Boolean引用类型:

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

注意静态工厂方法是不同于设计模式中的工厂模式的。这节里所描述的静态工厂方法在设计模式中没有直接的对等模式。一个类可以不提供构造器而只提供静态工厂方法给它的客户端,或者除了提供构造器以外还可以提供静态工厂方法给它的客户端。提供静态工厂方法而不是公共构造器既有优点也有缺点。

静态工厂方法的一个优点是,不同于构造器,它们有方法名字。如果一个构造方法需要一些参数,但是无论从这些参数中的某些参数,或者从这些参数本身,都不能体现出使用这些参数将构造的对象,那么一个名字取得好的静态工厂方法比构造方法使用起来更加简单,而且这样的客户端代码读起来也更容易。例如,这个构造器BigInteger(int, int, Random),它返回的BigInteger可能是一个素数,本可以更好的用一个静态工厂方法BigInteger.probablePrime去做(这个方法在Java 1.4加入了Java)。

根据类名一般一个类可以写一个构造器。众所周知,开发人员可以用重载去写两个构造方法,只要这两个构造方法的参数列表中的参数类型的顺序不同,就可以解决这个问题。这可不是一个好主意。使用这种API的用户永远都不能记住哪个构造器是哪一个,最终会不小心选择了一个错误的构造器。看这种代码的人如果不参考类的文档的话就不会知道这段代码做什么。因为他们有了名字,所以静态工厂方法没有上面段落所说的那些限制。在一个类可能需要多个构造器并且使用同一个名字的情况下,只要用静态工厂方法去代替他们,并且通过小心地起不同的方法名来强调它们就可以了。

静态工厂方法的第二个优点是,不同于构造器,它不是在每次调用的时候都需要创建出来一个对象的。这就允许了不变的类去使用之前创造好的实例,或者在创建后将实例缓存下来,每次都去返回它们从而避免了创建不必要的多个对象。Boolean.valueOf(boolean)这个方法就强调了这一点:它永远都不会创建对象。这种方法跟享元模式相似。在需要去频繁的创建相同的对象,而且尤其在创建他们很耗资源的时候,会极大的提高性能。重复调用静态工厂方法返回相同对象的这种能力允许了类本身能够严格的控制在什么时候都有什么实例是存在的。这样做的类被称为是instance-controlled。写这种类是有几个原因的。Instance-controlled允许一个类去保证它是单例的,或者是不可实例化的。而且,它允许了immutable value class(item 17)能够保证没有两个相等的实例是存在的:有且只有a == b,那么a.equals(b)成立。这是享元模式的基础。枚举类型(item 34)会提供这种保证。

静态工厂方法的第三个优点是,不同于构造器,他们可以返回声明返回类型的任何子类对象。这使得你在选择返回对象的类的时候给予了你相当大的灵活性。

这种灵活性的一个应用是,一个API可以无需将类设置为public就可以返回对象。通过这种方式隐藏实现类可以设计出非常紧凑的API。这种方法把自己(个人理解是对象构建)交给了interface-based frameworks(Item 20),在这些框架里面接口作为了静态工厂方法的天然的返回类型。在java 8以前,接口不能有静态方法。依据约定,接口叫Type的一些静态工厂方法过去被放在了不可实例化的名字是Types的组合类(item 4)中。举个栗子,java集合框架为框架里面的接口提供了45个多种用途的实现,比如提供了unmodifiable集合,synchronized集合,诸如此类的。几乎所有的实现都是在一个不可实例化的类(java.util.Collections)里面通过静态工厂方法去发布出去的。这些返回对象的类都不是公共的。比起为了给每一个便利的实现都去写一个类而去发布45个独立的公共类,集合框架的API是小的太多了。它不仅减少了大量的API,而且减少了概念上的重量:开发人员为了使用API必须掌握的知识点的数量还有难度。开发人员知道返回的对象就是接口所精简阐述的API。所以就不必为了这个实现类去阅读额外的类的文档。而且,使用这样一个静态工厂方法需要客户端用一个接口类型而不是实现类来接收返回对象,这是一个好的实践(item 64)。正如java 8里面的,接口不能包含静态方法的限制被去掉了,所以一般情况下为了接口提供一个不能实例化的组合类是没有理由的。许多公共的静态成员本来能够放在接口本身却被放在这样的一个类里面,应该被放在接口自身里面了。注意然而将大量的实现代码隐藏于这些静态方法之后,放在另外一个package-private的类里面也许仍然是必要的。因为java 8需要接口里面的所有静态成员都是公有的。Java 9允许接口有私有的静态方法,但是静态的变量还有静态成员类仍然需要是公共的。

静态工厂方法的第四个优点是,根据输入参数的不同返回对象的类会随着不同的调用变化的。任何声明的返回对象的子类都是允许的。返回对象的类也可以随着不同的发布而变化。

EnumSet类(item 36)没有公共的构造器,只有静态工厂方法。在OpenJDK实现里面,他们返回的对象是它的两个子类的其中一个,具体是哪一个依赖于隐含的枚举类型的大小:如果像大多数类型一样它有64或者更少的元素,这个静态工厂方法返回RegularEnumSet的实例,背后是一个long(long是8个字节64位);如果枚举类型有65个或者更多的元素,这个静态工厂方法返回一个JumboEnumSet实例,背后是一个long数组。

对于客户端而言它是看不见这两种实现类的存在的。如果RegularEnumSet不再为小的枚举类型提供性能优势,它可以在没有什么不好的影响的情况下在未来的发布中删除掉。相似的,如果新的EnumSet的实现类可以为性能提供优势,将来的发布中也会增加这种或者更多的实现。客户端既不知道也不关心他们通过工厂拿到的类的对象;他们关心的是,它是只要是EnumSet的某种子类。

静态工厂方法的第五个优点是,当包含静态工厂方法的类被写的时候,返回对象的类不一定要存在。这种灵活的静态工厂方法为service provider frameworks提供了基础,比如java数据库连接的API (JDBC)。Service provider framework框架是一个系统,这个系统会提供一个服务,并且这个系统使得客户端可以获取到具体实现,达到客户端和具体实现的解耦。

在service provider framework中有3个重要的组成部分:一个服务接口,它代表了具体实现;一个提供者注册API,提供者使用它来注册具体实现;还有一个服务访问API,客户端用它去获得服务的具体实例。服务访问API会允许客户端指出选择一个实现的条件。如果缺少了这个条件,这个API会返回一个默认实现的实例,或者允许客户端去遍历所有可用的实现。这个服务访问API就是灵活的静态工厂方法,它组成了service provider framework的基础。

Service provider framework的第四个可选的组成部分是服务提供接口,它描述了一个工厂对象,这个工厂对象用来产生这个服务接口的实例。在服务提供接口缺失的情况下,具体的实例必须靠反射来实例化。就JDBC而言,Connection扮演了服务接口的角色,DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API,还有Driver是服务提供接口。service provider framework模式有很多变体。例如,比起提供者返回的对象,服务访问API可以给客户端返回更加丰富的服务接口。这是桥接模式。依赖注入框架(Item 5)就可以被看做强大的服务提供者。从Java 6起,平台就包含了具有广泛作用的服务提供框架,java.util.ServiceLoader,所以,你不用,而且大多不应该去写你自己的。JDBC没有用ServiceLoader,因为它先出现。

只提供静态工厂方法而没有public或者protected的构造方法的主要限制是不能被继承。比如,在Collections框架里继承任何方便用的实现类都是不可能的。引起争论的是,这也算得上是因祸得福,因为它会鼓励开发人员去使用组合而不是继承(item 18),并且对于不变的类型来说也是需要的(item 17)。

第二个静态工厂方法的缺点是,开发人员很难找到它们。他们不会像构造器一样在API文档里面分出来,所以怎么找到使用静态工厂方法而不是构造器来实例化一个类就会变得困难。Javadoc工具也许在未来会关注到静态工厂方法。与此同时,通过多注意类中的静态工厂,或者多注意那些坚持基本命名惯例的接口文档来降低这个问题。这儿有一些给静态工厂方法的基本命名。这个列表离所有的情况还差很多:

• from —一种类型转换方法,需要一个参数,返回包含这个参数的这个类型的实例,例如:

Date d = Date.from(instant);

• of —一个聚合方法,需要多个参数,返回包含这些参数的这个类型的实例,例如:

Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);

• valueOf —比起from还有of更加冗长的选择,例如:

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); 

• new Type—就像newInstance,只是当工厂方法在一个不同的类中使用。Type是工厂方法返回对象的类型,例如:

FileStore fs = Files.getFileStore(path); 

• type—对于getType还有newType的精简的替代,比如:

List<Complaint> litany = Collections.list(legacyLitany);
综上所述,静态工厂方法和公共构造方法都有它们的用途,需要了解它们的好处都是要付出的。通常情况下静态工厂方法是更受欢迎的,所以尽量避免不考虑静态工厂方法就直接提供公共的构造器。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值