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

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

  • 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

Image placeholder
Mryangzai
未设置
  46人点赞

没有讨论,发表一下自己的看法吧

推荐文章
1.3. 工厂方法模式(Factory Method)

1.3.1.目的 对比简单工厂模式的优点是,您可以将其子类用不同的方法来创建一个对象。 举一个简单的例子,这个抽象类可能只是一个接口。 这种模式是「真正」的设计模式,因为他实现了S.O.L.I.D原则

《Effective Go》中文翻译召集

《EffectiveGo》是学习Go编程必读的官方文档,内容包含对Go语法、技巧、编码风格等说明。文档永久地址:《高效的Go编程》欢迎正在学习Go的同学参与。如何参与?进入文档页面《高效的Go编程》

pymysql fetchone () , fetchall () , fetchmany ()

最近在用python操作mysql数据库时,碰到了下面这两个函数,标记一下: 1.定义 1.1fetchone(): 返回单个的元组,也就是一条记录(row),如果没有结果则返回None 1.2fet

1.0. 抽象工厂模式(Abstract Factory)

1.1.1.目的 在不指定具体类的情况下创建一系列相关或依赖对象。通常创建的类都实现相同的接口。抽象工厂的客户并不关心这些对象是如何创建的,它只是知道它们是如何一起运行的。 1.1.2.UML图 1

Cache 和 Buffer 的区别在哪里?

Cache和Buffer是两个不同的概念,简单的说,Cache是加速“读”,而buffer是缓冲“写”,前者解决读的问题,保存从磁盘上读出的数据,后者是解决写的问题,保存即将要写入到磁盘上的数据。在很

深入了解Nodejs Buffer的使用

JavaScript起初为浏览器而设计,没有读取或操作二进制数据流的机制。Buffer类的引入,则让NodeJS拥有操作文件流或网络二进制流的能力。Buffer基本概念Buffer对象的内存分配不是在

构造方法及方法重载笔记

a:当基本数据类型的变量作为方法的参数传递时,新参变量的改变不会影响到实参。b:当引用数据类型的变量作为方法的参数传递时,新参变量的改变会影响到实参。

defer vs return

defer看起来与try...catch类似,其实有许多不为人知的小技巧 defer官方行文defer先进后出,对return进行一些扫尾工作。这意味着使用该函数在返回值之前,defer函数内是可以

jquery中deferred对象是什么?

jquery中deferred对象是什么?Deferred是JQuery的一个延迟对象,意思是函数延迟到某个点才开始执行,改变执行状态的方法有两个(成功:resolve和失败:reject),分别对应

在Jenkins中发布react 静态项目常用shell脚本

功效:服务器git永远同步,远程,本地有更改文件或文件夹会被重置掉,保持和远程仓库一致 rsync同步文件 永久链接:https://shudong.wang/10705.html获取git远程仓库

IBM Spectrum Protect 8.1.7在AIX7.1上的安装和配置

                                                本文作者: 谷铁柏摘要:    本文章主要讲述IBMSpectrumProtect8.1.7版本在AIX

MVVM原理(Object.defineProperty和订阅者模式)

想着去了解vue的mvvm数据驱动是怎么实现的,百度中看了这篇文章,demo很好。其他文章只是讲到defineProperty的set,get。彻底理解Vue中的Watcher、Observer、De

走进希捷无锡工厂 感受智能制造的魅力

希捷,那个硬盘厂商?搞智能制造?没错,不用怀疑,跟我去希捷无锡工厂转一转便知。无锡作为希捷的工厂所在地:这里,是高质量硬盘的诞生地;是希捷创新未来的发源地;在这里,希捷一次又一次突破与创新数据存储;在

深圳流水线工厂,我差点和主管打了起来 | 十年系列

01.写在前面十年前,我还是象牙塔中数学系的一名普通的大三学生。九年前,我是富士康流水线工厂的一名工人。六年前,我包里揣着3000元RMB来北漂。三年前,我在一家互联网金融公司做到了技术负责人。两年前

React v16.9 中unsafe的生命周期函数

https://zh-hans.reactjs.org/b...Unsafe的生命周期 componentWillMount→UNSAFE_componentWillMount 没有用过,不描述

PHP跌出前十,铁打的 Python 连续3年第一:IEEE Spectrum 2019编程语言排行榜出炉

Python势头不减,依旧第一,而且进一步拉开了与其他语言的差距。这一结果,来自IEEESpectrum2019年度编程语言排行榜。这已经是Python连续3年保持第一。在Python之下,第二交椅的

PHP构造函数的继承问题

PHP构造函数的继承问题 关于类继承,总有一个常见的问题,这就是构造函数的使用。子类实例化时会执行父类的构造函数吗?如果是这样,倘若子类也有自己的构造函数会怎么样?子类构造函数在父类构造函数之后执行,

GoWeb教程_14.1. 静态文件支持

我们在前面已经讲过如何处理静态文件,这小节我们详细的介绍如何在beego里面设置和使用静态文件。通过再介绍一个twitter开源的html、css框架bootstrap,无需大量的设计工作就能够让你快

Go语言高级编程_2.9 静态库和动态库

2.9静态库和动态库 CGO在使用C/C++资源的时候一般有三种形式:直接使用源码;链接静态库;链接动态库。直接使用源码就是在import"C"之前的注释部分包含C代码,或者在当前包中包含C/C++源

解读2019华为第001号文件:AI时代软件开发的第一要义是可信

晓查发自凹非寺量子位出品|公众号QbitAIAI加持,万物互联、万物智能。我们在享受科技进步的同时,软件开发行业却面临着更大的挑战。过去,软件出现安全问题或许仅仅意味着经济损失,但当走向产业互联网时代

嗨!你的 2019 晒好封存了吗?快来看程序老兵的 2019 吧!

时间过得真是太快快快了,2019还剩下最后几个小时了。回望即将过去的这一年,老兵哥做了不少事情,有计划内的,也有计划外的,当然还有不少事情没做。赶在最后时刻晒一晒我的2019年,希望从成绩荣誉中获得一

HBase实战:记一次Safepoint导致长时间STW的踩坑之旅

本文记录了HBase中Safepoint导致长时间STW此问题的解决思路及办法。过程记录现象:小米有一个比较大的公共离线HBase集群,用户很多,每天有大量的MapReduce或Spark离线分析任务

AI赌神升级!无惧bluff,6人局德扑完胜世界冠军,训练只用了8天

大数据文摘出品作者:曹培信、宁静2017年年初,BrainvsAI的德州扑克人机大战在卡耐基梅隆大学(CMU)落幕,由4名人类职业玩家组成的人类大脑不敌人工智能程序Libratus。获胜后人类还遭到了

vue中的diff算法详解

1.当数据发生变化时,vue是怎么更新节点的?要知道渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,有没有可能我们只更新我们修改的

Eclipse发布:2019年物联网开发者调查

如果你想了解一项重要技术的未来,那么先看开发人员在做什么。考虑到这一点,在EclipseFoundation对 1700 多名物 联 网开 发 人 员 (pdf) 进行的一项新调查中,可以获得对整个物