静态工厂方法VS构造器
我之前已经介绍过关于构建者模式(Builder Pattern)的一些内容,它是一种很有用的模式用于实例化包含几个属性(可选的)的类,带来的好处是更容易读、写及维护客户端代码。今天,我将继续介绍对象创建技术。
在我看来,下面这个类是非常有用的例子。有一个RandomIntGenerator 类,产生随机的int类型的整数。如下所示:
- public class RandomIntGenerator {
- private final int min;
- private final int max;
- public int next() {...}
- }
这个生成器接收最大值和最小值两个参数并且生成介于两者之间的随机数。注意到两个属性min和max被final修饰,所以必须初始化它们。可以在定义它们时就初始化或者通过构造器来初始化。通过构造器初始如下:
- public RandomIntGenerator(int min, int max) {
- this.min = min;
- this.max = max;
- }
现在,我们要提供给这样一个功能,客户端仅设置一个最小值然后产生一个介于此值和Integer.MAX_VALUE之间的整数。所以我们添加了第二个构造器:
- public RandomIntGenerator(int min) {
- this.min = min;
- this.max = Integer.MAX_VALUE;
- }
到目前为止,一切正常。但是,我们同样要提供一个构造器设置一个最大值,然后产生一个介于Integer.MIN_VALUE和最大值的整数。我们添加第三个构造器如下:
- public RandomIntGenerator(int max) {
- this.min = Integer.MIN_VALUE;
- this.max = max;
- }
如果你这样做了,将会出现编译错误:Duplicate method RandomIntGenerator(int) in type RandomIntGenerator。那里出错了?毫无疑问,问题在于构造器没有名字。因此,一个类仅有一个特定方法签名的构造器。同样的,你不能定义方法签名相同的(返回值、名称、参数类型及个数均相同)两个方法。这就是为什么当我们试着添加构造器RandomIntGenerator(int max) 时,会得到上述的编译错误,原因是我们已经有一个构造器 RandomIntGenerator(int min)。
像这样的情况我们该如何处理呢?幸好有其他方式可以使用:静态工厂方法,通过使用简单的公共静态方法返回一个类的实例。你可能在无意识中已经使用过这种技术。你有没有写过Boolean.valueOf?,就像下面这样:
- public static Boolean valueOf(boolean b) {
- return (b ? TRUE : FALSE);
- }
将静态工厂应用到RandomIntGenerator类,得到
- public class RandomIntGenerator {
- private final int min;
- private final int max;
- private RandomIntGenerator(int min, int max) {
- this.min = min;
- this.max = max;
- }
- public static RandomIntGenerator between(int max, int min) {
- return new RandomIntGenerator(min, max);
- }
- public static RandomIntGenerator biggerThan(int min) {
- return new RandomIntGenerator(min, Integer.MAX_VALUE);
- }
- public static RandomIntGenerator smallerThan(int max) {
- return new RandomIntGenerator(Integer.MIN_VALUE, max);
- }
- public int next() {...}
- }
注意到构造器被private修饰确保类仅能通过静态工厂方法来初始化。并且当你使用RandomIntGenerator.between(10,20)而不是new RandomIntGenerator(10,20)来产生整数时你的意图被清晰的表达了。值得注意的是,这个技术和Gang of Four的工厂设计模式不同。此外,任何类可以提供静态工厂方法替代构造器。那么此种技术的优点和缺点是什么呢?我们已经提到过静态工厂方法的第一个优点:静态工厂方法拥有名字。这有两个直接的好处:
1.我们可以给静态方法提供一个有意义的名字
2.我们可以给静态方法提供参数类型、参数个数相同的几个构造器,在传统构造器中是不能这样做的
另一个优点是:不像构造器,静态工厂方法不需要每次在调用时创建一个新的对象。当使用不可变类(immutable class)产生常量对象用于常用值且避免了不必要的重复对象是非常有用的。上面的列子Boolean.valueOf完美的表明了这一点。注意到这个静态方法返回均为不可变Boolean对象TRUE或者FALSE。
第三个优点是静态工厂方法的返回对象的类型可以是返回类型的任意子类型。这使你随意更改返回类型而不用担心影响到客户端代码成为了可能。此外,你可以隐藏实现类并且构建基于接口的API(Interface-based API)。通过一个例子来说明。
记得刚开始的RandomIntGenerator类吗?我们让他复杂一点。假设我们现在想提供不仅能产生整型,而且能产生其他数据类型如String, Double或者Long的随机生成器。这些生成器将有一个next()方法返回一个特定类型的随机对象,所以我们可以先定义一个接口如:
- public interface RandomGenerator<T> {
- T next();
- }
RandomIntGenerator 的第一个实现类如下:
- class RandomIntGenerator implements RandomGenerator<Integer> {
- private final int min;
- private final int max;
- RandomIntGenerator(int min, int max) {
- this.min = min;
- this.max = max;
- }
- public Integer next() {...}
- }
String类型的生成器如:
- class RandomStringGenerator implements RandomGenerator<String> {
- private final String prefix;
- RandomStringGenerator(String prefix) {
- this.prefix = prefix;
- }
- public String next() {...}
- }
注意到所有的类及类的构造器被定义为包私有范围(默认的可见范围)。这意味着除本包之外的客户端代码无法创建这些生成器的实例。那我们该怎么办?提示:它以“static”开始,以“methods”结束。考虑下面这个类:
- public final class RandomGenerators {
- // Suppresses default constructor, ensuring non-instantiability.
- private RandomGenerators() {}
- public static final RandomGenerator<Integer> getIntGenerator() {
- return new RandomIntGenerator(Integer.MIN_VALUE, Integer.MAX_VALUE);
- }
- public static final RandomGenerator<String> getStringGenerator() {
- return new RandomStringGenerator('');
- }
- }
RandomGenerators类成为了一个不可实例化的工具类,它与静态工厂方法没有什么区别。在同一个包中,不同的生成器类可以高效的获取和实例化这些类。但是,有意思的部分出现了。注意到这些方法仅返回RandomGenerator 接口,这才是客户端代码真正需要的。如果它获得一个RandomGenerator它就知道调用next()然后得到一个随机的整数。
假设下月我们编写了一个新的高效的整数生成器。只要让这个新类实现了RandomGenerator我们更换静态方法中的返回类型,那么所有客户端代码神奇的使用了新的实现。
像RandomGenerators 的类在JDK及第三方类库是相当常见的。你可以在Collections(java.util)及Lists, Sets or Maps(in Guava)看到许多例子。命名习惯是一样的:如果你有一个接口命名为Type,那么在不可实例化类中的静态方法名就是Types。
最后一个优点是静态工厂是实例化参数类很简洁。你曾经见过像这样的代码吗?
- Map<String, List<String>> map = new HashMap<String, List<String>>();
你在代码的同一行重复相同的参数两次!如果右边的赋值可以从左边被推断出来,那将是很优雅的做法。使用静态方法就可以。下面的代码取自 Guava’s Maps 类:
- public static <K, V> HashMap<K, V> newHashMap() {
- return new HashMap<K, V>();
- }
所以现在我们的客户端代码变成如下:
- Map<String, List<String>> map = Maps.newHashMap();
相当的优雅,对吗?这样的能力被成为类型推断(Type interface)。值得一提的是Java7通过使用方括号运算符(diamond operator)引入了类型推断。所以,如果你使用Java7你可以前面的例子如:
- Map<String, List<String>> map = new HashMap<>();
静态工厂的主要缺点是类中没有public或者protected的构造器无法被继承。但事实上,在某种情况下这个一件好事,因为鼓励开发者优先使用组合而不是继承(favor composition over inheritance)。
总的来讲,静态工厂方法提供了许多优点,当你在使用时唯一的不足之处实际上也不会是一个问题。所以,评估一下你的类是否更适合静态工厂,拒绝急切的自动提供公共的构造器。
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 静态工厂方法VS构造器
静态工厂方法VS构造器的更多相关文章
- 【Effective Java读书笔记】创建和销毁对象(一):考虑使用静态工厂方法代替构造器
类可以提供一个静态方法,返回类的一个静态实例,如Boolean包装类的一个获取实例的静态方法 public static Boolean valueOf(boolean b) { return (b ...
- 改善JAVA代码01:考虑静态工厂方法代替构造器
前言 系列文章:[传送门] 每次开始新的一本书,我都会很开心.新书新心情. 正文 静态工厂方法代替构造器 说起这个,好多可以念叨的.做了一年多的项目,慢慢也有感触. 说起构造器 大家很明白,构造器 ...
- Java - 用静态工厂方法代替构造器
Effective Item - 考虑用静态工厂方法代替构造器我们有两种常见的方法获得一个类的实例: 公有的构造器 提供静态工厂方法(static factory method) 相对公有的构造器,静 ...
- Effective java读书札记第一条之 考虑用静态工厂方法取代构造器
对于类而言,为了让client获取它自身的一个实例,最经常使用的方法就是提供一个共同拥有的构造器. 另一种放你发,也应该子每一个程序猿的工具箱中占有一席之地.类能够提供一个共同拥有的静态 工厂方法.它 ...
- 【读书笔记 - Effective Java】01. 考虑用静态工厂方法代替构造器
获取类的实例有两种方法: 1. 提供一个公有的构造器(最常用). 2. 提供一个公有的静态工厂方法(static factory method). // 静态工厂方法示例 public static ...
- Effective Java 读书笔记(一):使用静态工厂方法代替构造器
这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...
- effective java 3th item1:考虑静态工厂方法代替构造器
传统的方式获取一个类的实例,是通过提供一个 public 构造器.这里有技巧,每一个程序员应该记住.一个类可以对外提供一个 public 的 静态工厂方法 ,该方法只是一个朴素的静态方法,不需要有太多 ...
- Tips1:考虑用静态工厂方法代替构造器
用静态工厂方法来代替构造器为外界提供对象 描述: 静态工厂方法代替构造器来给外界提供对象,创建对象依然是由构造器来完成的 创建对象和提供对象: 创建对象的方式: 构造器 提供对象来哦方式: 构造器 类 ...
- 高效JAVA之用静态工厂方法代替构造器
程序员这行干的久了,总会染上一些恶习,我就染上一个让人深恶痛绝,自己却津津乐道的习惯,还不想改的那种,它可以叫做强迫症,也可以叫做洁癖.那就是我不允许我的IDEA出现一点点警告,什么黄色背景,绿色波浪 ...
随机推荐
- JavaScript高级程序设计学习笔记--BOM
window对象 BOM的核心对象是window,它表示浏览器的一个实例.在浏览器中,window对象有双重角色,它既是通过JavaScript访问浏览器窗口的一个接口,又是ECMScript规定的G ...
- .NET LINQ 限定符操作
限定符操作 限定符运算返回一个 Boolean 值,该值指示序列中是否有一些元素满足条件或是否所有元素都满足条件. 方法 方法名 说明 C# 查询表达式语法 Visual Basic 查询表 ...
- 使用MyBatis Generator自动创建代码( SSM框架)
步骤: 1.找到该文件目录 (上图文件下载地址:http://download.csdn.net/download/u014617413/9668872) 2.修改generatorConfig.xm ...
- Arch Linux中文乱码解决
Arch Linux中文乱码解决 1.安装中文字体 pacman -S wqy-zenhei ttf-fireflysung (flash乱码) ---乱码的原因就是缺少中文字体的支持,下载文泉驿 ...
- Build2016上值得一看的大数据相关Session
(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:Build2016开完很久了,现在才来回顾下,就说说那些和大数据相关的Session, ...
- C#对象序列化与反序列化
C#对象序列化与反序列化(转载自:http://www.cnblogs.com/LiZhiW/p/3622365.html) 1. 对象序列化的介绍.......................... ...
- Spring Boot 部署与后台运行服务配置
http://blog.csdn.net/catoop/article/details/50588851
- 从零开始山寨Caffe·零:必先利其器
工作环境 巧妇有了米炊 众所周知,Caffe是在Linux下写的,所以长久以来,大家都认为跑Caffe,先装Linux. niuzhiheng大神发起了caffe-windows项目(解决了一些编译. ...
- [我的试题]Test of String
1.前言 这是我出的第一套题目,话说感觉有点晚了,还是在向总安排下出的.我被安排的是字符串方面的内容,这应该相对而言是比较小众的知识点吧,但是一样的有作用的,也有很神的题目.所谓是NOIP模拟题,其实 ...
- About_Web
成功网站的三要诀:内容.设计.营销 内容为王: 高质量的内容会促使网站走向成功.首先,用户有需求,他们需要被感动,被娱乐,被有料的内容和产品所吸引.漂亮的背景和亮骚的特效可能会有所助益,但终究只是辅助 ...