菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
46
0

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

原创
05/13 14:22
阅读数 1122

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

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

什么是静态工厂方法

先看一个例子,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类型的对象,这个类是我自己定义的,然后同事就跟我说你创建这个类需要好多参数,创建起来很麻烦,其实我早就贴心地建好了静态工厂方法,方便别人使用。但是别人在用你的类的时候不一定会看你的方法,创建对象的时候下意识的都是想着构造器。

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

  • 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 一样,但 create 或者 newInstance 保证每次调用都返回一个新的实例,例如: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 litany = Collections.list(legacyLitany);

总结

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

参考

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

发表评论

0/200
46 点赞
0 评论
收藏