Effective Java (第三版)——条目一:考虑静态工厂方法替代构造器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq442270636/article/details/81842749

       Consider static factory methods instead of constructors


我们获得一个实例的传统方法是让类提供一个公有的构造器。还有一个技术应该成为每一个编程人员的工具。一个类能够提供一个公开的静态工厂方法,它是一个简单的静态方法:用于返回一个类的实例。这里有一个布尔类型的简单例子。这个方法将基本类型的布尔值转换为布尔对象引用。

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

注意静态工厂方法和来自设计模式的工厂模式的区别。这里描述的静态工厂方法不等效于设计模式的工厂模式。

类可以为其客户端提供静态工厂方法,而不是公共构造方法。提供一个静态工厂方法而不是公共构造器存在优点和缺点。

优点:

使用静态工厂方法的一个优点是:不同于构造器,他们拥有自己名称。如果构造方法的参数本身并不描述被返回的对象,那么让一个静态工厂选择更好的名字去使用是相对容易的并且返回客户端的结果是更易于阅读的。举个例子,构造器BigInteger(int,int,Random),他返回一个可能是素数的大整数,可以使用静态工厂方法更好的表达为BigInteger.probablePrime(这个方法被增加于Java4)。

一个类只能有一个带有指定签名的构造器。编程人员通常知道如何避开这一限制:通过提供两个构造器,它们的参数列表只在参数类型的顺序上有所不同。实际上这并不是个好主意。面对这样的API,用户永远也记不住该用哪个构造器,结果常常会调用错误的构造器。并且,读到使用了这些构造器的代码时,如果没有参考类的文档,往往不知所云。

因为他们拥有名字,静态工厂方法不受上面所说的限制。当一个类需要多个带有相同签名的构造器时,就用静态工厂方法代替构造器,并且慎重地选择名称以便突出它们之间的区别

使用静态工厂方法的第二个优点是:不同于构造器,每一次被重复调用不会去创造一个新的对象。这允许不可变类使用预先构建的实例,或者在构造时缓存实例,并且避免重复创建非必须的重复对象。Boolean.valueOf(boolean)方法说明这个技巧:它从不创造对象,这个技术类似于Flyweight模式(享元模式)。它极大的提高了性能如果经常请求重复的对象,特别是在创建的对象开销比较大的时候。

静态工厂方法为重复的调用返回同一对象的能力有助于类总能严格控制在某个时刻哪些实例应该存在。这种类被叫做实例受控的类。编写实例受控类有这几个原因:确保类是一个单例或者不可实例化。同样的,他保证值不可变的类不会同时存在两个相同的实例,这是FlyWeight模式的基础。枚举类型提供类这种保证。

使用静态工厂方法的第三个优点是:不同于构造函数,静态工厂方法可以返回原返回类型的任何子类型对象。这让你在选择返回类型提供给你极大灵活性。

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

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

Collections框架API的规模要比它之前输出的45个单独的公共类要小得多,每个类有个便利类的实现。不仅是API的大部分减少了,还包括概念上的权重:程序员必须掌握的概念的数量和难度,才能使用API。程序员知道返回的对象恰好有其接口指定的API,因此不需要为实现类读阅读额外的类文档。此外,使用这种静态工厂方法需要客户端通过接口而不是实现类来引用返回的对象,这通常是良好的实践(条目64)。

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

使用静态工厂的第四个优点:返回对象的类可以根据输入参数的不同而不同 任何被声明的子类返回类型都是允许的。 返回对象的类也可以随着版本而不同。

EnumSet类没有公共构造方法,只有静态工厂。 在OpenJDK实现中,它们根据底层枚举类型的大小返回两个子类中的一个的实例:如果大多数枚举类型具有64个或更少的元素,静态工厂将返回一个RegularEnumSet实例, 返回一个long类型;如果枚举类型具有六十五个或更多元素,则工厂将返回一个JumboEnumSet实例,返回一个long类型的数组。

这两个实现类的存在对于客户是不可见的。 如果RegularEnumSet不再为小枚举类型提供性能优势,则可以在未来版本中将其移除,而不会产生任何不良影响。 同样,未来的版本可能会添加EnumSet的第三个或第四个实现,如果它证明有利于性能。 客户既不知道也不关心他们从工厂返回的对象的类别; 他们只关心它是EnumSet的一些子类。

使用静态工厂的第5个优点是:类返回的对象不需要存在,在编写包含该方法的类时。这种灵活的静态工厂方法构成了服务提供者框架的基础,比如Java数据库连接API(JDBC)。服务提供者框架是提供者实现服务的系统,并且系统使得实现对客户端可用,从而将客户端从实现中分离出来。

服务提供者框架中有三个基本组:服务接口,它表示实现;提供者注册API,提供者用来注册实现;以及服务访问API,客户端使用该API获取服务的实例。服务访问API允许客户指定选择实现的标准。在缺少这样的标准的情况下,API返回一个默认实现的实例,或者允许客户通过所有可用的实现进行遍历。服务访问API是灵活的静态工厂,它构成了服务提供者框架的基础。

服务提供者框架的一个可选的第四个组件是一个服务提供者接口,它描述了一个生成服务接口实例的工厂对象。在没有服务提供者接口的情况下,必须对实现进行反射实例化。在JDBC的情况下,Connection扮演服务接口的一部分,DriverManager.registerDriver提供程序注册API、DriverManager.getConnection是服务访问API,Driver是服务提供者接口。

服务提供者框架模式有许多变种。 例如,服务访问API可以向客户端返回比提供者提供的更丰富的服务接口。 这是桥接模式[Gamma95]。 依赖注入框架可以被看作是强大的服务提供者。 从Java 6开始,平台包含一个通用的服务提供者框架java.util.ServiceLoader,所以你不需要,一般也不应该自己编写。 JDBC不使用ServiceLoader,因为前者早于后者。

不足:

第一个缺点是:仅仅提供静态工厂方法,没有公共或者受保护的构造函数不能被子类化。比如说不能将任何方便实现的类子类化在集合框架中,可以这是因祸得福,因为它鼓励编程人员去使用组合去替代继承并且是不可变类型。

第二个缺点是:编程人员很难找到它。它并不如构造函数在API文档里面突出,所以它非常困难找出实例化一个提供静态工厂方法而不是构造函数。Javadocgo工具可能某一天会对静态工厂方法注意。同时,你能通过将注意力吸引到类或接口文档中的静态工厂以及遵守通用的命名约定来减少这个问题。下面是一些静态工厂方法的常用名称。以下清单并非完整:

from——一个类型转换方法,它接受单个参数并返回此类型的相应实例
例如:Date d = Date.from(instant);
of——一个聚合方法,接受多个参数并返回该类型的实例,并把他们合并在一起
例如:Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
valueOf——from和to更为详细的替代方式
例如:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
instance或getinstance——返回一个由其参数(如果有的话)描述的实例,但不能说它具有相同的值,例如:StackWalker luke = StackWalker.getInstance(options);
create 或 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—— 一个简洁的替代方式getType 和 newType,
例如:List<Complaint> litany = Collections.list(legacyLitany);

总结

静态工厂方法和公共构造函数都可以被使用,并且理解他们的相对优点是值得的。通常静态工厂是更好的,所以要避免没有第一时间考虑静态工厂方法而去使用公共构造函数。

更多文章请关注公众号:每天学Java。想获得更多最新面试提醒请进入小程序:每天学Java

 公众号二维码:                                                                                          小程序二维码:

                       

猜你喜欢

转载自blog.csdn.net/qq442270636/article/details/81842749