静态工厂方法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出现一点点警告,什么黄色背景,绿色波浪 ...
随机推荐
- CentOS7 屏幕亮度的命令行管理
1.安装:yum install xgamma 设置亮度:xgamma -gamma n( 0.1 < n < 10.0 ,可以根据自己的喜好设置 2.shell 系统调节亮度调用的是/s ...
- jquery图片轮播
<html> <head> <title>position</title> <style type="text/css"> ...
- Redis学习笔记8--Redis发布/订阅
发布订阅(pub/sub)是一种消息通信模式,主要的目的是解耦消息发布者和消息订阅者之间的耦合,这点和设计模式中的观察者模式比较相似.pub /sub不仅仅解决发布者和订阅者直接代码级别耦合也解决两者 ...
- [jquery]模仿radio单项选择
今天写了个小功能,模仿radio单选, //单选收货地址 $(".wuliu-table-to").find(".called").click(function ...
- javascript学习 真正理解DOM脚本编程技术背后的思路和原则
本文学习来源于<javascriptDOM编程艺术>仅作笔记 学会怎样才能利用DOM脚本编程技术以一种既方便自己更体贴用户的方式去充实和完善你们的网页. 循序渐进:从最核心的内容开始,逐步 ...
- Android之EACCES (Permission denied)与Permission denied异常探密
话说,Accipiter君,最近又开始怒学Android了,记得刚开始还是09年学的,现在的手机还是华为出的最早的一款Android手机C8500,那时候就想好好学习Android,赚点小钱,可是~~ ...
- 我为什么喜欢用C#来做并发编程
(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:就语言和运行时层面,C#做并发编程一点都不弱,缺的是生态和社区. 硅谷才女朱赟(我的家 ...
- linux中grep命令详解
http://jingyan.baidu.com/article/76a7e409e72777fc3b6e158a.html
- 诡异的C语言实参求值顺序
学了这么久的C语言,竟然第一次碰到这么诡异的实参求值顺序问题,大跌眼镜.果然阅读面太少了! #include<iostream> void foo(int a, int b, int c) ...
- PHP实现快速排序、插入排序、选择排序
1.快速排序 快速排序(Quicksort)是对冒泡排序的一种改进.由C. A. R. Hoare在1962年提出.它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都 ...