静态工厂方法VS构造器

原文链接 作者:Jose Luis 译者:yxiaomou

我之前已经介绍过关于构建者模式(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)。
总的来讲,静态工厂方法提供了许多优点,当你在使用时唯一的不足之处实际上也不会是一个问题。所以,评估一下你的类是否更适合静态工厂,拒绝急切的自动提供公共的构造器。 

时间: 2024-09-22 08:19:40

静态工厂方法VS构造器的相关文章

Effective Java (1) 考虑用静态工厂方法代替构造器

一.前言 从今天开始计划用一个月的时间,通过写读书笔记的方式来记录阅读这本Java领域经典中的经典书籍 - Effective Java过程中自己所思所想,以备以后查阅,同时分享出去也希望可以帮助到其他人,废话不多说,现在开始第一篇:创建和销毁对象. 二.考虑用静态工厂方法代替构造器 ①. 一般我们有什么办法可以创建对象呢? 方法1: 使用类公有构造器. 方法2:使用类的静态方法返回一个实例. ②. 使用静态方法创建对象有什么优点? 优点1: 静态工厂方法的名字是由我们自己命名,而构造方法必须与

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

正文  静态工厂方法代替构造器 说起这个,好多可以念叨的.做了一年多的项目,慢慢也有感触. 说起构造器 大家很明白,构造器可以让我们在何处何地获取自身或者他人一个实例.我们是无忌惮的使用着 new 却从来没考虑过人家的感受.其实new ,new一个对象,就是开辟一块内存空间给这个对象.如果何处何地,都new的话,漫山遍野... 五颜六色的new ,本质却一样   一句话:构造器虽是万能,但是要珍惜. 再谈谈 静态工厂方法 静态工厂方法,顾名思义,只是一个返回类实例的静态方法.这里有个不切当的比喻

Spring使用静态工厂方法创建Bean

1. 使用静态工厂方法创建Bean     使用静态工厂方法创建Bean实例时,class属性也必须指定,但此时class属性并不是指定Bean实例的实现类,而是静态工厂类.因为Spring需要知道是用哪个工厂来创建Bean实例.另外,还需要使用factory-method来指定静态工厂方法名,Spring将调用静态工厂方法(可能包含一组参数),来返回一个Bean实例,一旦获得了指定Bean实例,Spring后面的处理步骤与采用普通方法创建Bean实例则完全一样.需要注意的是,当使用静态工厂方法

Android设计模式系列之工厂方法模式_Android

工厂方法模式,往往是设计模式初学者入门的模式,的确,有人称之为最为典型最具启发效果的模式. android中用到了太多的工厂类,其中有用工厂方法模式的,当然也有很多工厂并不是使用工厂方法模式的,只是工具管理类. 今天以ThreadFactory举例说明一下简单工厂模式和工厂方法模式. 工厂方法模式,Factory Method,简单的方式,不简单的应用. 1.意图 定义一个用于创建对象的接口,让子类决定实例化哪个类.工厂方式模式使一个类的实例化延迟到其子类. 热门词汇:虚构造器 延迟 创建对象

设计模式:工厂方法模式(Factory Method)和抽象工厂模式(Abstact Factory)

 在面向对象编程中, 最通常的方法是一个new操作符产生一个对象实例,new操作符就是用来构造对象实例的.但是在一些情况下, new操作符直接生成对象会带来一些问题.举例来说, 许多类型对象的创造需要一系列的步骤: 你可能需要计算或取得对象的初始设置; 选择生成哪个子对象实例; 或在生成你需要的对象之前必须先生成一些辅助功能的对象. 在这些情况,新对象的建立就是一个 "过程",不仅是一个操作,像一部大机器中的一个齿轮传动. 分类  工厂模式主要是为创建对象提供过渡接口,以便将创建对象的

JavaScript设计模式之工厂方法模式介绍_javascript技巧

1. 简单工厂模式 说明:就是创建一个工厂类,里面实现了所对同一个接口的实现类的创建. 但是好像JavaScript 好像没有 接口 这号东西,所以我们去掉接口这个层; 当然,我们这里的 实现类 下的成员变量,方法应该都是一样的: 例如:这时举短信发送跟邮件发送的例子; 1>. 邮件发送[实现]类 复制代码 代码如下: function MailSender() {     this.to = '';     this.title = '';     this.content = ''; } M

C++设计模式编程中简单工厂与工厂方法模式的实例对比_C 语言

简单工厂模式实例题目:实现计算器的输入2个数和运算符,得到结果 工程结构: (1)头文件 COperationFactory.h(运算符工厂类) (2)源文件 SimpleFactory.cpp(客户端应用类,主函数所在) (3)运算类 COperation.cpp(运算符基类) COperation.h COperationAdd.h(加法运算符子类,继承于COperation) COperationDiv.h (除法运算符子类,继承于COperation) COperationMul.h (

设计模式——1工厂方法模式(Factory Method)

1.工厂方法模式(Factory Method)工厂方法模式分为三种:11.普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建.举例如下:(我们举一个发送邮件和短信的例子)首先,创建二者的共同接口:[java] view plaincopy public interface Sender { public void Send(); } 其次,创建实现类: [java] view plaincopy public class MailSender implements Sen

设计模式详解之工厂方法模式

工厂方法模式(Factory Method)分为3种: 1.普通工厂模式 就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建.首先看下关系图: 我们以一个例子来讲解:发送短信和发送邮件(具有共同的接口:发送) 接口: public interface Sender { /* * 发送邮件或者短消息的共同接口 */ public void sender(); } 实现类: /* * 发送邮件的实现类 */ public class MailSender implements Sender