我之前已经介绍过关于构建者模式(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构造器的更多相关文章

  1. 【Effective Java读书笔记】创建和销毁对象(一):考虑使用静态工厂方法代替构造器

    类可以提供一个静态方法,返回类的一个静态实例,如Boolean包装类的一个获取实例的静态方法 public static Boolean valueOf(boolean b) { return (b ...

  2. 改善JAVA代码01:考虑静态工厂方法代替构造器

    前言 系列文章:[传送门]   每次开始新的一本书,我都会很开心.新书新心情. 正文 静态工厂方法代替构造器 说起这个,好多可以念叨的.做了一年多的项目,慢慢也有感触. 说起构造器 大家很明白,构造器 ...

  3. Java - 用静态工厂方法代替构造器

    Effective Item - 考虑用静态工厂方法代替构造器我们有两种常见的方法获得一个类的实例: 公有的构造器 提供静态工厂方法(static factory method) 相对公有的构造器,静 ...

  4. Effective java读书札记第一条之 考虑用静态工厂方法取代构造器

    对于类而言,为了让client获取它自身的一个实例,最经常使用的方法就是提供一个共同拥有的构造器. 另一种放你发,也应该子每一个程序猿的工具箱中占有一席之地.类能够提供一个共同拥有的静态 工厂方法.它 ...

  5. 【读书笔记 - Effective Java】01. 考虑用静态工厂方法代替构造器

    获取类的实例有两种方法: 1. 提供一个公有的构造器(最常用). 2. 提供一个公有的静态工厂方法(static factory method). // 静态工厂方法示例 public static ...

  6. Effective Java 读书笔记(一):使用静态工厂方法代替构造器

    这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...

  7. effective java 3th item1:考虑静态工厂方法代替构造器

    传统的方式获取一个类的实例,是通过提供一个 public 构造器.这里有技巧,每一个程序员应该记住.一个类可以对外提供一个 public 的 静态工厂方法 ,该方法只是一个朴素的静态方法,不需要有太多 ...

  8. Tips1:考虑用静态工厂方法代替构造器

    用静态工厂方法来代替构造器为外界提供对象 描述: 静态工厂方法代替构造器来给外界提供对象,创建对象依然是由构造器来完成的 创建对象和提供对象: 创建对象的方式: 构造器 提供对象来哦方式: 构造器 类 ...

  9. 高效JAVA之用静态工厂方法代替构造器

    程序员这行干的久了,总会染上一些恶习,我就染上一个让人深恶痛绝,自己却津津乐道的习惯,还不想改的那种,它可以叫做强迫症,也可以叫做洁癖.那就是我不允许我的IDEA出现一点点警告,什么黄色背景,绿色波浪 ...

随机推荐

  1. tp5 中 model 的查询方法

    实例化模型后调用查询方法,可以写任何想要的查询(推荐) public function select(){ $user = model('User'); $data = $user -> ) - ...

  2. .NET LINQ 相等运算

    相等运算      如果两个序列的对应元素相等且这两个序列具有相同数量的元素,则视这两个序列相等. 方法 方法名 说明 C# 查询表达式语法 Visual Basic 查询表达式语法 更多信息 Seq ...

  3. MyEclipse无法删除项目下的文件

    想删除老版本的jar包或文件,却怎么也删不了, 总是提示Problems encountered while deleting resources. Could not delete 后来关闭myec ...

  4. javascript判断给定字符串是否是回文

    //判断给定字符串是否是回文     function isPalindrome(word) {         var s = new Stack();         for (var i = 0 ...

  5. MyBatis学习(四)MyBatis和Spring整合

    MyBatis和Spring整合 思路 1.让spring管理SqlSessionFactory 2.让spring管理mapper对象和dao. 使用spring和mybatis整合开发mapper ...

  6. [BI项目记]-搭建代码管理环境之服务端

    上一篇介绍如何搭建环境进行文档版本的管理,这篇主要介绍搭建环境进行代码版本的管理. 即使是BI项目也要进行代码版本管理.代码版本管理的工具有很多,VSS, SVN等都是当下大家经常提起的,这里主要介绍 ...

  7. AgileEAS.NET SOA 中间件平台.Net Socket通信框架-简单例子-实现简单的服务端客户端消息应答

    一.AgileEAS.NET SOA中间件Socket/Tcp框架介绍 在文章AgileEAS.NET SOA 中间件平台Socket/Tcp通信框架介绍一文之中我们对AgileEAS.NET SOA ...

  8. redis数据类型之—String

    (1)String 简单介绍 string是redis中最基本的数据类型,一个字符串类型的值存储的最大容量是1GB. (2)String 常用命令

  9. LinQ和ADO.Net增删改查 备忘

    是否些倦了 SqlConnection conn=new SqlConnection();一系列繁冗的代码? 来试试Linq吧 查: using System.Data.SqlClient; name ...

  10. Superslide插件无效的问题

    用Superslide像往常那样导入JQ和SuperSlide后,首页焦点图不会变,就像SuperSlide失效了一样,为什么??? 排查了一圈最后发现是JS导入顺序的问题,必须先导入JQ,再导入Su ...