`

静态工厂方法VS构造器

阅读更多

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

我之前已经介绍过关于构建者模式 (Builder Pattern)的一些内容,它是一种很有用的模式用于实例化包含几个属性(可选的)的类,带来的好处是更容易读、写及维护客户端代码。今天,我将继续介绍对象创建技术。
在我看来,下面这个类是非常有用的例子。有一个RandomIntGenerator 类,产生随机的int类型的整数。如下所示:

1 public class RandomIntGenerator {
2 private final int min;
3 private final int max;
4  
5 public int next() {...}
6 }

这个生成器接收最大值和最小值两个参数并且生成介于两者之间的随机数。注意到两个属性min和max被final修饰,所以必须初始化它们。可以在定义它们时就初始化或者通过构造器来初始化。通过构造器初始如下:

1 public RandomIntGenerator(int min, int max) {
2     this.min = min;
3     this.max = max;
4 }

现在,我们要提供给这样一个功能,客户端仅设置一个最小值然后产生一个介于此值和Integer.MAX_VALUE之间的整数。所以我们添加了第二个构造器:

1 public RandomIntGenerator(int min) {
2     this.min = min;
3     this.max = Integer.MAX_VALUE;
4 }

到目前为止,一切正常。但是,我们同样要提供一个构造器设置一个最大值,然后产生一个介于Integer.MIN_VALUE和最大值的整数。我们添加第三个构造器如下:

1 public RandomIntGenerator(int max) {
2     this.min = Integer.MIN_VALUE;
3     this.max = max;
4 }

如果你这样做了,将会出现编译错误:Duplicate method RandomIntGenerator(int) in type RandomIntGenerator。那里出错了?毫无疑问,问题在于构造器没有名字。因此,一个类仅有一个特定方法签名的构造器。同样的,你不能定义方法签名相同的(返回值、名称、参数类型及个数均相同)两个方法。这就是为什么当我们试着添加构造器RandomIntGenerator(int max) 时,会得到上述的编译错误,原因是我们已经有一个构造器 RandomIntGenerator(int min)。

像这样的情况我们该如何处理呢?幸好有其他方式可以使用:静态工厂方法,通过使用简单的公共静态方法返回一个类的实例。你可能在无意识中已经使用过这种技术。你有没有写过Boolean.valueOf?,就像下面这样:

1 public static Boolean valueOf(boolean b) {
2     return (b ? TRUE : FALSE);
3 }

将静态工厂应用到RandomIntGenerator类,得到

01 public class RandomIntGenerator {
02     private final int min;
03     private final int max;
04  
05     private RandomIntGenerator(int min, int max) {
06         this.min = min;
07         this.max = max;
08     }
09  
10     public static RandomIntGenerator between(int max, int min) {
11         return new RandomIntGenerator(min, max);
12     }
13  
14     public static RandomIntGenerator biggerThan(int min) {
15         return new RandomIntGenerator(min, Integer.MAX_VALUE);
16     }
17  
18     public static RandomIntGenerator smallerThan(int max) {
19         return new RandomIntGenerator(Integer.MIN_VALUE, max);
20     }
21  
22     public int next() {...}
23 }

注意到构造器被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()方法返回一个特定类型的随机对象,所以我们可以先定义一个接口如:

1 public interface RandomGenerator<T> {
2     T next();
3 }

RandomIntGenerator 的第一个实现类如下:

01 class RandomIntGenerator implements RandomGenerator<Integer> {
02     private final int min;
03     private final int max;
04  
05     RandomIntGenerator(int min, int max) {
06         this.min = min;
07         this.max = max;
08     }
09  
10     public Integer next() {...}
11 }

String类型的生成器如:

1 class RandomStringGenerator implements RandomGenerator<String> {
2     private final String prefix;
3  
4     RandomStringGenerator(String prefix) {
5         this.prefix = prefix;
6     }
7  
8     public String next() {...}
9 }

注意到所有的类及类的构造器被定义为包私有范围(默认的可见范围)。这意味着除本包之外的客户端代码无法创建这些生成器的实例。那我们该怎么办?提示:它以“static”开始,以“methods”结束。考虑下面这个类:

01 public final class RandomGenerators {
02     // Suppresses default constructor, ensuring non-instantiability.
03     private RandomGenerators() {}
04  
05     public static final RandomGenerator<Integer> getIntGenerator() {
06         return new RandomIntGenerator(Integer.MIN_VALUE, Integer.MAX_VALUE);
07     }
08  
09     public static final RandomGenerator<String> getStringGenerator() {
10         return new RandomStringGenerator('');
11     }
12 }

RandomGenerators类成为了一个不可实例化的工具类,它与静态工厂方法没有什么区别。在同一个包中,不同的生成器类可以高效的获取和实例化这些类。但是,有意思的部分出现了。注意到这些方法仅返回RandomGenerator 接口,这才是客户端代码真正需要的。如果它获得一个RandomGenerator它就知道调用next()然后得到一个随机的整数。
假设下月我们编写了一个新的高效的整数生成器。只要让这个新类实现了RandomGenerator我们更换静态方法中的返回类型,那么所有客户端代码神奇的使用了新的实现。
像RandomGenerators 的类在JDK及第三方类库是相当常见的。你可以在Collections(java.util)及Lists, Sets or Maps(in Guava)看到许多例子。命名习惯是一样的:如果你有一个接口命名为Type,那么在不可实例化类中的静态方法名就是Types。
最后一个优点是静态工厂是实例化参数类很简洁。你曾经见过像这样的代码吗?

1 Map<String, List<String>> map = new HashMap<String, List<String>>();

你在代码的同一行重复相同的参数两次!如果右边的赋值可以从左边被推断出来,那将是很优雅的做法。使用静态方法就可以。下面的代码取自 Guava’s Maps 类:

1 public static <K, V> HashMap<K, V> newHashMap() {
2   return new HashMap<K, V>();
3 }

所以现在我们的客户端代码变成如下:

1 Map<String, List<String>> map = Maps.newHashMap();

相当的优雅,对吗?这样的能力被成为类型推断(Type interface)。值得一提的是Java7通过使用方括号运算符(diamond operator)引入了类型推断。所以,如果你使用Java7你可以前面的例子如:

1 Map<String, List<String>> map = new HashMap<>();

静态工厂的主要缺点是类中没有public或者protected的构造器无法被继承。但事实上,在某种情况下这个一件好事,因为鼓励开发者优先使用组合而不是继承(favor composition over inheritance)。
总的来讲,静态工厂方法提供了许多优点,当你在使用时唯一的不足之处实际上也不会是一个问题。所以,评估一下你的类是否更适合静态工厂,拒绝急切的自动提供公共的构造器。

分享到:
评论

相关推荐

    java静态工厂方法详细解析——使用静态工厂方法代替构造器

    1. **静态工厂方法有名称**:与构造器只能通过类名来区分不同实例化方式不同,静态工厂方法可以根据功能命名,使得代码更易读,更直观地理解方法的作用。 2. **静态工厂方法不用在每次调用时创建新对象**:静态工厂...

    java 构造器的调用

    7. **构造器与工厂方法**: - 除了构造器,还可以使用工厂方法创建对象。工厂方法是类中的静态方法,返回类的新实例。这种方法可以提供更灵活的实例化逻辑,比如延迟初始化或返回子类实例。 总结,Java中的构造器...

    静态工厂demo

    1. **名称可变性**:静态工厂方法可以拥有与类名不同的名称,这样可以根据方法名的不同返回不同类型的实例,比构造器更灵活。 2. **无需公开构造器**:如果类的实例不应直接通过`new`关键字创建,静态工厂可以提供一...

    Java静态工厂方法的实例详解

    Java静态工厂方法是一种特殊的工厂方法,它可以返回类的实例,而不是通过构造器来获取实例。静态工厂方法通常以valueOf、getInstance、newInstance等命名,具有三个特点:具名、环保、多子。 具名:静态工厂方法...

    Effective Java读书笔记.pdf

    Effective Java是一本关于Java编程语言的经典书籍,本笔记主要总结了Java语言的发展历程、静态工厂方法的应用、构造器模式的使用等重要知识点。 一、Java语言的发展历程 Java语言的发展可追溯到1991年,当时由...

    effective-java.pdf

    标题“effective-java.pdf”与描述“effective-java.pdf”表明本文档是关于Java...以上就是文档提供的关于Java中静态工厂方法与构造方法对比的知识点,它强调了静态工厂方法在代码设计中的应用以及带来的多方面优势。

    Effective-Java读书笔记(上)

    2. **使用静态工厂方法**:如果一个类既提供了静态工厂方法也提供了构造器,则通常推荐使用静态工厂方法来创建对象,以减少不必要的对象创建。 通过以上讨论可以看出,使用静态工厂方法不仅可以让代码更加简洁易懂...

    javade8种构造共12页.pdf.zip

    6. **静态工厂方法**:虽然不是真正的构造器,但静态工厂方法可以返回类的实例,且允许类隐藏其构造器。这种模式在某些情况下,如单例模式或控制实例计数时非常有用。 7. **Java 8的构造器注入**:在Java 8中,可以...

    Spring 实例化Bean的三种方式

    本文将深入探讨Spring中实例化Bean的三种主要方式:构造器注入、静态工厂方法注入以及实例工厂方法注入。 #### 1. 构造器注入(Constructor Injection) 构造器注入是指通过调用Bean类的构造器来创建Bean实例。...

    javascript工厂模式和构造函数模式创建对象方法解析.docx

    构造函数模式使用一个函数作为构造器,该函数通常首字母大写以示区别。通过使用 `new` 关键字调用构造函数,可以创建出具有相同属性和方法的对象实例。 **2.2 构造函数模式的实现** 下面是使用构造函数模式重写...

    论构造方法的方法论.zip

    当需要更复杂的初始化逻辑时,可以使用有参构造器,它们可以根据传入参数的不同值来创建不同状态的对象。此外,构造方法还可以链式调用,即一个构造方法调用另一个构造方法,以避免代码重复。 在面向对象设计原则中...

    《Effective Java》读书分享.pptx

    Builder 模式是一种构建对象的方法,不直接生成想要的对象,而是利用必要参数调用构造器(或者静态工厂)得到一个 builder 对象,然后在 builder 对象上调用类似 setter 的方法,设置可选参数,最后调用无参的 build...

    effective Java(第3版)各章节的中英文学习参考(已完成).zip

    目录(Contents)第 2 章 创建和销毁对象(创建和气氛对象)第二章简介(章节介绍)第 1 条考虑静态工厂方法而不是构造函数(考虑以静态工厂方法代替构造函数)Item 2: 当面对许多构造函数参数时考虑构建器(在面对...

    Java创建对象的5种方式.java

    静态工厂方法与工厂方法类似,但它是类静态方法。这种方式不需要创建工厂类的实例,可以直接调用类的静态方法得到对象。这种方式可以避免使用`new`关键字,并且可以返回类的子类型实例,而无需显式指定子类。例如:...

    03-主流框架-01-spring.doc

    静态工厂方法是属于类的静态方法,不需要先创建工厂实例就能调用,而实例工厂则需要先有一个工厂实例,然后通过该实例调用工厂方法来创建对象。这两种工厂方法都可以通过配置XML来指定,Spring会根据配置的工厂方法...

    Spring Bean重复执行两次(实例被构造两次)问题分析

    4. **静态工厂方法或Singleton作用域**:如果使用静态工厂方法创建Bean,且未正确配置单例模式,那么每次请求都会创建新的Bean实例。对于Singleton作用域的Bean,Spring保证在整个容器生命周期内只有一个实例,如果...

    工厂模式简单示例代码

    private StaticProduct() {} // 私有化构造器,防止外部直接实例化 // 静态工厂方法 public static StaticProduct createProduct() { // 可以在此添加逻辑,比如根据条件返回不同类型的实例 return new ...

    浅谈springioc实例化bean的三个方法

    Spring IOC实例化Bean有三种方法:构造器实例化、静态工厂方法实例化和实例工厂方法实例化。每种方法都有其优缺,选择哪种方法取决于实际需求。 在实际开发中,我们可以根据需要选择合适的实例化方法。例如,如果...

Global site tag (gtag.js) - Google Analytics