转载

[憨读记 之 Effective Java] 01-用静态工厂方法代替构造器

书的第一章是创建和销毁对象,接下来的几篇也都是围绕这个展开。

本篇对应书中的第一条:用静态工厂方法代替构造器。

什么是静态工厂方法

先看一个例子,Boolean类中有如下构造器

public Boolean(boolean value) {
    this.value = value;
}

同时,还提供了如下的静态方法,也可以返回Boolean类实例

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

这个就是静态工厂方法:用一个静态方法来对外提供自身实例。(非官方定义)

要注意,这里的静态工厂方法跟设计模式中的工厂模式并没有什么对应关系。篇幅原因(主要是懒,这里就不详细说了。

静态工厂方法的优势

优势1:有名称。

构造器的一个缺点是可能没法对创建的对象有明确的描述,例如构造器 BigInteger(int, int, Random) 返回的BigInteger可能为素数,但是使用 BigInteger probablePrime(int, Random) 方法会更加明确。

构造器的另一个缺点是一个类只能有一个带有指定签名的构造器,如果想避开这个限制,可以提供两个构造器,它们的参数列表只是参数类型的顺序不一样,但这个会对使用方造成迷惑。静态工厂方法则不受这个限制。

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

比如上面的 Boolean.valueOf 方法,返回值已经预先构建好了,不会新创建对象。同时,平常经常使用的单例模式,一般也都是通过静态工厂方法实现的。这个比起每次创建一个新对象的成本要低很多。

优势3:可以返回原返回类型的任何子类型的对象。

提供了极大的灵活性,对于面向接口编程非常适用。比如日常的工作中,可以定义方法的返回值为一个接口,在实际返回的时候可能是该接口的任意实现类。

另外,还可以返回对象,同时又不会使对象的类变成公有的。

java.util.Collections 中有很多这种使用方式:

public static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {
    return new SetFromMap<>(map);
}

而这个 SetFromMap 就是一个私有的类

private static class SetFromMap<E> extends AbstractSet<E> implements Set<E>, Serializable{
    ...
}

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

这个看文本大概就明白什么意思,跟上一点有点相似,不过更强调的是通过参数值返回不同的类,有可能是基于性能的考虑,也有可能是基于业务的考虑。比如根据数组的大小返回不同的实现。

优势5:在编写包含该方法的类时,返回的对象的类不需要存在。

这是 服务提供者框架 的基础,典型的应用场景就是JDBC,具体的实现类都是各个数据库驱动包中提供的,编写jdbc相关代码时并不存在。

可能你第一次听说服务提供者框架这个名词,但是你应该早就接触过了。

服务提供者框架是指:多个服务提供者实现一个服务,系统为客户端提供多个实现,并把他们从多个实现中解耦出来。

服务提供者的改变对它们的客户端是透明的,这样提供了更好的可扩展性。例如,JDBC,JMS等就是用了服务提供者框架。

有四个组件

  • Service Interface:服务接口,将服务通过抽象统一声明,供客户端调用、由各个服务提供者具体实现。
  • Provider Registration API:服务提供者注册API,用于系统注册服务提供者,使得客户端可以访问它实现的服务。
  • Service Access API:服务访问API,用户客户端获取相应的服务。
  • Service Provider Interface:服务提供者接口,这些服务提供者负责创建其服务实现的实例。(可选)

看不懂别着急,可以对应到JDBC

  • Service Interface: Connection ,客户端的调用都是基于 Connection
  • Provider Registration API: DriverManager.registerDriver ,注册服务提供者的API,数据库驱动会调用这个API把自己注册。
  • Service Access API: DriverManager.getConnection 获取服务的API。
  • Service Provider Interface: Driver ,用于创建 Connection

这个模式很好用,最近的代码中一直在用这个模式,有兴趣的可以去看看JDBC源码。

静态工厂方法的劣势

劣势1:没有公共或受保护构造方法的类不能被子类化

这个其实还好,如果是自己写的类,有需要的话可以在提供静态工厂方法的同时提供公有的或者受保护的构造器。如果还不行,可以使用组合而不是继承,应该知道是啥意思哈,不懂后边也会写(不是这一篇,估计是10篇以后了)。

劣势2:很难找到它们

别笑,这个真实地发生了。我写了一个服务,参数是一个 ValueFilter 类型的对象,这个类是我自己定义的,然后同事就跟我说你创建这个类需要好多参数,创建起来很麻烦,其实我早就贴心地建好了静态工厂方法,方便别人使用。但是别人在用你的类的时候不一定会看你的方法,创建对象的时候下意识的都是想着构造器。

下面是一些静态工厂方法的惯用名称(照抄

Date d = Date.from(instant);
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
StackWalker luke = StackWalker.getInstance(options);
Object newArray = Array.newInstance(classObject, arrayLen);
FileStore fs = Files.getFileStore(path);
BufferedReader br = Files.newBufferedReader(path);
List litany = Collections.list(legacyLitany);

总结

在需要提供实例的时候不要第一反应就提供公有的构造器,可以优先考虑静态工厂。

参考

  • 服务提供者框架(Service Provider Framework)

看到了这里一定是真爱了,关注微信公众号【憨憨的春天】第一时间获取更新

[憨读记 之 Effective Java] 01-用静态工厂方法代替构造器

原文  https://segmentfault.com/a/1190000021486456
正文到此结束
Loading...