《Effective Java》笔记01:考虑用静态工厂方法替代构造器
对于类而言,为了创建对象,最常见的方法就是提供一个公有的构造器。
还有一种方法,类可以提供一个公有的静态工厂方法(static factory method
)(不同于设计模式中的工厂方法),它是一个返回类实例的静态方法。下面是一个来自 Boolean 的简单示例,这个方法将 boolean 基本类型值,String 类型转换成了一个 Boolean 对象引用:
public final class Boolean implements java.io.Serializable, Comparable<Boolean> {
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
public static Boolean valueOf(String s) {
return toBoolean(s) ? TRUE : FALSE;
}
private static boolean toBoolean(String name) {
return ((name != null) && name.equalsIgnoreCase("true"));
}
}
公有构造器的方式的缺点
- 只能通过 new className() 的方式来实现
- 每次调用必然返回一个新的对象
- 返回类型就是该类
使用静态工厂方法的优势
静态工厂方法是有名称的
使用有适当的名称的静态工厂方法,会更加便于阅读。
如果一个类有多个构造器,而且构造器的参数没有确切的描述被返回的对象,如果 2 个构造函数拥有相同个数和类型的参数,虽说可以改变顺序,但这样会不便于阅读。
例如:一个类 Complex
(复数,就是数学课上学的,有实数部和虚数部),对它的构造可能有这样 2 种需求:
- 分别给出实数部和虚数部来构造之;
- 基于极坐标来构造(提供“半径”和“角度”)
由于这两种构造方式,都是由两个 float 型参数的,对于构造函数将无能为力,而静态工厂方法可以从方法名来区别开,而且还带来了易于辨识的好处:
public class Complex {
private final float re;
private final float im;
private Complex(float re, float im){
this.re = re;
this.im = im;
}
public static Complex valueOf(float re, float im){
return new Complex(re, im);
}
public static Complex valueOfPolar(float r, float theta){
return new Complex((float)(r * Math.cos(theta)), (float)(r * Math.sin(theta)));
}
}
不必在每次调用它们的时候都创建一个新的对象
我们调用静态工厂方法返回的可能是缓存的一个对象,而不是新对象。可以进行重复利用,从而避免创建不必要的重复对象。
如果程序经常请求创建相同的对象,并且创建的代价很高,则静态工厂方法可以极大地提升性能。
前面提到的 Boolean.valueOf(boolean) 便说明了这项技术,还有单例,枚举(enum)类型。
可以返回原返回类型的的任何子类型的对象
这样在选择返回对象的类时就有了更大的灵活性。返回的对象可以不是公有的,java 的集合框架(java.util.Collections
)就采用了这种实现,返回的子类都是非公有的,外部无法访问,对调用者来说并不知道是哪个子类的对象,隐藏了实现细节,减少了 API 的数量,提高了易用性。例如,EnumSet 中,创建一个空的 enum set
// 如果,枚举类型的元素个数不超过 64 个则返回 RegalarEnumSet 实例,否则返回 JumboEnumSet 实例。
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
// 这两个实现类的存在对于客户端来说是不可见的。
// 如果 RegularEnumSet 不能再给小的枚举类型提供性能优势,就可能从未来的发行版本中将它删除,不会造成不良的影响。
// 同样的了,如果证明对性能要好处,也可能在未来的发行版本中添加第三甚至第四个 Enum 实现。
// 客户端永远也不知道也不关心他们从工厂方法得到的对象的类,他们只关心他是 EnumSet 的某个子类即可。
使用这种静态工厂方法时,甚至要求客户端通过接口来引用被返回的对象,而不是通过它的实现类来引用被返回的对象,这是一种良好的习惯。
返回对象所属的类,在编写包含该方法时可以不存在
静态工厂方法返回对象所属的类,在编写包含该静态工厂方法时可以不必存在。这种灵活的静态方法构成了服务提供者框架(Service Provide Framework)的基础,例如 JDBC(Java Database Connectivity) API。服务提供者框架是这样一个系统:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把他们从多个实现中解耦出来。
服务提供者框架中有三个重要的组件:服务接口(Service Interface),这是提供者实现的;提供者注册API(Provide Registration API),这是系统用来注册实现的,让客户端访问它们的;服务访问API(Service Access API),是客户端用来获取服务的实例的。
服务访问 API 一般允许但是不要求客户端指定某种选择提供者的条件。如果没有这样的规定,API 就会返回默认实现的一个实例。服务访问 API 是“灵活的静态工厂”,它构成了服务提供者框架的基础。
服务提供者框架的第四个组件是可选的:服务提供者接口(Service Provide Interface),这些提供者负责创建其服务实现的实例。如果没有服务提供者接口,实现就按照类名称注册,并通过反射方式进行实例化。
对于 JDBC 来说,Connection
就是它的服务接口,DriverManager.registerDriver
是提供者注册 API,DriverManager.getConnection
是服务访问 API,Driver
就是服务提供者接口。
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 使用服务提供者接口
Class.forName("com.mysql.jdbc.Driver");// 不使用上面的
// 由于 MySQL 在 Driver 类的实现中自己注册了一次,而我们又注册了一次,于是会导致 MySQL 驱动被注册两次
// 创建 MySQL 的 Driver 对象时,导致了程序和具体的 MySQL 驱动绑死在了一起,在切换数据库时需要改动 Java 代码,所以不使用 new
// 获取数据库连接
java.sql.Connection conn = java.sql.DriverManager
.getConnection("jdbc:mysql:///databasename?user=root&password=root");
从 Java 6 开始,平台包含一个通用的服务提供者框架 java.util.ServiceLoader
,所以你不需要,一般也不应该自己编写。 JDBC 不使用 ServiceLoader,因为前者早于后者。
简单实现
服务提供者框架(Service Provide Framework)
服务接口(Service Interface)
// 相当于 Connection 接口,由 Sun 提供
public interface Service {
}
服务提供者接口(Service Provide Interface)
// 相当于 Driver 接口,由第三方厂家实现
public interface Provider {
Service newService();
}
无法实例化的类,用来注册和访问服务,
// 好比 DriverManager
public class Services{
private Services(){}
// 服务提供者 map
private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
// 默认的服务提供者名字
public static final String DEFAULT_PROVIDER_NAME = "<def>";
// 服务提供者注册 API,即注册工厂实现,相当于 DriverManager.registerDriver
public static void registerDefaultProvider(Provider p){
registerProvider(DEFAULT_PROVIDER_NAME, p);
}
// 注册 Provider
public static void registerProvider(String name, Provider p){
providers.put(name, p);
}
// 服务访问 API,向外界提供业务实现,相当于 DriverManager.getConnectio
public static Service newInstance(){
return newInstance(DEFAULT_PROVIDER_NAME);
}
public static Service newInstance(String name){
Provider p = providers.get(name);
if (p == null) {
throw new IllegalArgumentException("No Provider registered with name:" + name);
}
return p.newService();
}
}
使用静态工厂方法的缺点
类如果不含公有的或者受保护的构造器,就不能被子类化
对于公有的静态工厂所返回的非公有类,也同样如此。如,你想将 Collections Framework中的任何方便的实现类子类化,是不可能的。
但是这样有时候也有好处,即它鼓励程序员使用复合(compostion),而不是继承。
程序员很难找到它们
在 API 文档中,他们没有像构造器那样在 API 文档中明确标识出来,因此对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类,这是非常困难的。javadoc 工具总有一天会注意到静态工厂方法。同时,你通过在类或者接口注释中关注静态工厂,并遵守标准的命名习惯,也可以弥补这一劣势。
下面是静态工厂的一些惯用名称:
from
,A 类型转换方法,它接受单个参数并返回此类型的相应实例,例如:Date d = Date.from(instant);of
,一个聚合方法,接受多个参数并返回该类型的实例,并把他们合并在一起,例如:SetfaceCards = EnumSet.of(JACK, QUEEN, KING); valueOf
,不太严格讲,该方法返回的实例与它的参数具有相同的值。这样的静态工厂方法实际上是类型转换方法。例如:Integer.valueOf(1)instance
或getInstance
,返回的实例是通过方法的参数来描述的。但是不能够说与参数具有同样的值。对于单例 Singleton 来说,该方法没有参数,并返回唯一的实例。create
或newInstance
,向 getInstance 一样,但 newInstance 能够确保返回的每个实例都与所有其他实例不同。getType
,就像 getInstance 一样,但是在工厂方法处于不同的类的时候使用(子类)。Type 表示工厂方法所返回的对象类型。例如:FileStore fs = Files.getFileStore(path);newType
,就像 newInstance一样,但是在工厂方法处于不同的类的时候使用(子类)。Type 表示工厂方法所返回的对象类型。例如:BufferedReader br = Files.newBufferedReader(path);
总结
总之,静态工厂方法和公共构造方法都有它们的用途,并且了解它们的相对优点是值得的。通常,静态工厂更可取,因此避免在没有考虑静态工厂的情况下提供公共构造方法。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:《Effective Java》笔记01:考虑用静态工厂方法替代构造器
文章字数:2.6k
本文作者:Bin
发布时间:2016-04-28, 10:28:20
最后更新:2019-08-06, 00:42:23
原始链接:http://coolview.github.io/2016/04/28/Effective-Java/%E3%80%8AEffective%20Java%E3%80%8B%E7%AC%94%E8%AE%B001%EF%BC%9A%E8%80%83%E8%99%91%E7%94%A8%E9%9D%99%E6%80%81%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95%E6%9B%BF%E4%BB%A3%E6%9E%84%E9%80%A0%E5%99%A8/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。