天天看点

Effective Java - 第一条:用静态工厂方法代替构造器

第二章 创建和销毁对象

本章的主题是创建和销毁对象:何时以及如何创建对象,何时以及如何避免创建对象,如何确保它们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作。

第一条:用静态工厂方法代替构造器

        创建一个类的实例,可以选择提供一个公有的构造器,或者选择在类中提供一个公有的静态方法。

        注意:静态工厂方法与设计模式中的工厂方法模式不同。本条目所指的静态工厂方法并不直接对应于设计模式中的工厂方法。

提供静态工厂方法而不是公有的构造器,这样做既有优势,也有劣势。

优势

  • 优势1:它们有名称

                当一个类需要多个带有相同签名的构造器时,就用静态工厂方法代替构造器,并且仔细地选择名称以便突出静态工厂方法之间的区别。

  • 优势2:不必在每次调用它们的时候都创建一个新对象

                这使得不可变类可以预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的对象。类似于享元模式。

            静态工厂方法能够为重复的调用返回相同对象,这样有助于类在总能严格控制在某个时刻哪些实例应该存在。这种类被称为实例受控的类。 编写实例受控的类有几个原因。实例受控使得类可以确保它是一个Singleton或者是不可实例化的。它还使得不可变的值类可以确保不会存在两个相等的实例,即当且仅当a==b时,a.equals(b)才为true。这是享元模式的基础。枚举类型保证了这一点。

  • 优势3:它们可以返回类型的任何子类型的对象

                这种更大的灵活性的一种应用是,API可以返回对象,同时又不会使对象的类变成公有的。以这种方式隐藏实现类会使API变得非常简洁。这项技术适用于基于接口的框架,因为在这种框架中,接口为静态工厂提供了自然返回类型。

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

                现在的Collections Framework API比导出45个独立公有类的那种实现方式要小得多,每种便利实现都对应一个类。这不仅仅是指API数量上的减少,也是概念意义上的减少:为了使用这个API,用户必须掌握的概念在数量和难度上都减少了。程序员知道,被返回的对象是由相关的接口精确指定的,所以他们不需要阅读有关的文档。此外,使用这种静态工厂方法时,甚至要求客户端通过接口来引用被返回的对象,而不是通过它的实现类来引用被返回的对象,这是一种良好的习惯。

                从Java 8 版本开始,接口中不能包含静态方法的这一限制成为历史,因此一般没有任何理由给接口提供一个不可实例化的伴生类。已经被放在这种类中的许多公有的静态成员,应该被放到接口中去。但是要注意,仍然有必要将这些静态方法背后的大部分实现代码,单独放进一个包级私有的类中。这是因为在Java 8 中仍要求接口的所有静态成员都必须是公有的。在Java 9 中允许接口有私有的静态方法,但是静态域和静态成员仍然需要是公有的。

  • 优势4:返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值

                只要是已声明的返回类型的子类型,都是允许的。返回对象的类也可能随着发型版本的不同而不同。客户端永远不知道也不关心它们从工厂方法中得到的对象的类,它们只关心它是EnumSet的某个子类。

  • 优势5:返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在

                这种灵活的静态工厂方法构成了服务提供者框架的基础,例如JDBC API。服务提供者框架是指这样一个系统:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来。

            服务提供者框架中有三个重要的组件:服务接口,这是提供者实现的;提供者注册API,这是提供者用来注册实现的;服务访问API这是客户端用来获取服务的实例。服务访问API是客户端用来指定某种选择实现的条件。如果没有这样的规定,API就会返回默认实现的一个实例,或者允许客户端遍历所有可用的实现。服务访问API是“灵活的静态工厂”,它构成了服务提供者框架的基础。

            服务提供者框架的第四个组件服务提供者接口是可选的,它表示产生服务接口之实例的工厂对象。如果没有服务提供者接口,实现就通过反射方式进行实例化。对于JDBC来说,Connection就是器服务接口的一部分,DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API,Driver是服务提供者接口。

            服务提供者框架模式有着无数种变体。例如,服务访问API可以返回比提供者需要的更丰富的服务接口。这就是桥接模式。依赖注入框架可以被看作是一个强大的服务提供者。从Java 6 版本开始,Java平台就提供了一个通用的服务提供者框架java.util.ServiceLoader, 因此你不需要(一般来说也不应该)再自己编写了。JDBC不用ServiceLoader,因为前者出现得比后者早。

缺点

  • 缺点1:类如果不含公有的或者受保护的构造器,就不能被子类化

                例如,想要将Collections Framework 中的任何便利的实现类子类化,这是不可能的。但是这样也许会因祸得福,因为它鼓励程序员使用复合,而不是继承,这正是不可变类型所需要的。

  • 缺点2:程序员很难发现它们

                在API文档中,它们没有像构造器那样在API文档中明确标识出来,因此,对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类是非常困难的。Javadoc工具总有一天会注意到静态工厂方法。同时,通过在类或者接口注释中关注静态工厂,并遵守标准的命名习惯,也可以弥补这一劣势。下面是静态工厂方法的一些惯用名称。这里之列出了一小部分。

  1. from——类型转换方法,它只有单个参数,返回该类型的一个相对应的实例,例如:
  1. of——聚合方法,带有多个参数,返回该类型的一个实例,把它们合并起来,例如:
  1. valueOf——比from和of更繁琐的一种替代方法,例如:
  1. instance或者getInstance——返回的实例是通过方法的(如有)参数来描述的,但是不能说与参数具有相同的值,例如:
  1. create或者newInstance——像instance或者getInstance一样,但creat或者newInstance能够保证每次调用都返回一个新的实例,例如:
  1. getType——像getInsatance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法锁返回的对象类型,例如:
  1. newType——像newInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法锁返回的对象类型,例如:
  1. type——getType和newType的简版,例如:

        简而言之,静态工厂方法和公有构造器都各有用处,我们需要理解它们各自的长处。静态工厂经常更加适合,因此切忌第一反应就是提供公有的构造器,而不是考虑静态工厂。