考虑使用静态工厂方法替代构造函数

Posted origalom

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了考虑使用静态工厂方法替代构造函数相关的知识,希望对你有一定的参考价值。

1、何为静态工厂方法

  静态工厂方法就是一个返回类实例的静态方法。比如Boolean的valueof方法:

1 public static final Boolean TRUE = new Boolean(true);
2 public static final Boolean FALSE = new Boolean(false);
3 
4 public static Boolean valueOf(boolean b) {
5     return (b ? TRUE : FALSE);
6 }

 

2、为什么使用静态工厂方法

  和公共构造函数相比,静态工厂方法有许多的优点,下面进行一一介绍:

  (1)静态工厂方法是有名字的

    一个类的构造函数的名字都是相同的,为了区分两个不同的构造函数,只能通过参数类型的顺序或个数,这样,在没有阅读参考类文档的情况下,使用者很可能会错误的调用构造方法。

    而静态工厂方法不同,如果有一个精心选择的方法名称,那么就不会存在上面说的问题,便于程序的阅读和理解。

    例如,返回一个可能为素数的方法,在未查看文档的情况下,静态工厂方法比构造函数更容易理解:

 1 // 构造方法
 2 public BigInteger(int bitLength, int certainty, Random rnd) {
 3     BigInteger prime;
 4 
 5     if (bitLength < 2)
 6         throw new ArithmeticException("bitLength < 2");
 7     prime = (bitLength < SMALL_PRIME_THRESHOLD
 8                     ? smallPrime(bitLength, certainty, rnd)
 9                     : largePrime(bitLength, certainty, rnd));
10     signum = 1;
11     mag = prime.mag;
12 }
13 
14 
15 // 静态工厂方法
16 public static BigInteger probablePrime(int bitLength, Random rnd) {
17     if (bitLength < 2)
18         throw new ArithmeticException("bitLength < 2");
19 
20     return (bitLength < SMALL_PRIME_THRESHOLD ?
21             smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) :
22             largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd));
23 }        

  (2)静态工厂方法不用每次调用时候都创建一个新的对象

    静态工厂方法可以在任何时候对存在类的实例进行控制,在重复调用的时候返回相同的对象,避免创建不必要的重复对象。这样做可以极大的提高程序的性能,尤其是对那些创建成本非常高的类。比如在Boolean类的valueof方法中,它返回的是预先定义的对象,而这2个对象是静态不可变的,所以无论在何时调用这个方法,都会返回相同的对象,而不是重新创建对象。

  (3)静态工厂方法可以返回其子类型对象

    这样为选择返回对象提供了很大的灵活性。如java.util.EnumSet类,可以返回EnumSet的子类。

  (4)静态工厂方法可以根据输入参数的不同返回不同的类

    返回类型的任何子类都是可以的,返回对象的类可以根据参数不同而发生变化。

    比如java.util.EnumSet,其本身被abstract修饰,无法直接调用其构造函数。但可以调用其静态方法noneOf来创建对象,并且根据参数返回合适的对象:

 1 public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
 2     Enum<?>[] universe = getUniverse(elementType);
 3     if (universe == null)
 4         throw new ClassCastException(elementType + " not an enum");
 5 
 6     if (universe.length <= 64)
 7         return new RegularEnumSet<>(elementType, universe);
 8     else
 9         return new JumboEnumSet<>(elementType, universe);
10 }

    方法返回的2个类的存在对客户端来讲是不可见的,如果在未来版本中淘汰其中1个实例,不会造成任何的影响;同时,添加第三或第四个实现,也不会存在问题。

  (5)静态工厂方法在编写包含该方法的类时,返回对象的类可以不存在

     这构成了服务提供者框架的基础,如jdbc的API。服务提供者框架是提供者实现服务的系统,并且系统使得实现对客户端可用,从而将客户端从实现中分离出来。

    服务提供者框架基本组成:

    ① 服务接口:基本组件,表示实现;

    ② 提供者注册API:基本组件,提供者用来注册实现;

    ③ 服务访问API:基本组件,客户端使用该API获取服务的实例;允许客户指定选择实现的标准,在缺乏标准的情况下,API返回一个默认实现的实例,或者允许客户通过所有可用的实例进行遍历;

    ④ 服务提供者接口:可选组件,描述一个生成服务接口实例的工厂对象。

 

3、静态工厂方法的缺点

  (1)没有公共或者被保护的构造方法的类不能被子类化

    如果我们在类中,将构造方法设置为private,只提供静态工厂方法来构建对象,那么我们将不能通过继承来扩展该类。

    但是这也会鼓励我们使用组合而不是继承,并且是不可变类。

  (2)程序员很难找到静态工厂方法

    静态工厂方法不像构造方法那样在API文档中显得很突出,这样找到他们就变的非常困难。这样,我们只能通过将注意力引到类或接口文档中的静态方法以及通用的命名约定来减少这个问题。

    静态工厂方法的常用名称有:

    • from:接收单个参数并返回此类型的相应实例,如Date d = Date.from(instant);
    • of:接收多个参数并返回该类型的实例,并把他们合并在一起,如Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
    • valueof:from和of更为详细的替代方式,如BigInteger prime = BigInteger.valueof(Integer.MAX_VALUE);
    • instance或者getInstance:返回一个由其参数描述的实例,返回的实例可能拥有相同的值,如StackWalker luke = StackWalker.getInstance(options);
    • create或者newInstance:和instance或者getInstance类似,不同的是该方法保证每一个调用都返回一个新的实例,如Object newArray = Array.newInstance(classObject, arrayLen);
    • newType:和newInstance类似,但只有在工厂方法在不同类中时,才使用该方法,如BufferedReader br = Files.newBufferedReader(path);
    • type:getType和newType简洁的替代方式。

 

总之,静态工厂方法和构造函数都有各自的用途,但通常静态工厂方法是更可取的,所以应该避免在未考虑使用静态工厂方法的基础上就直接使用公共构造方法。      

 

以上是关于考虑使用静态工厂方法替代构造函数的主要内容,如果未能解决你的问题,请参考以下文章

Effective Java 第三版——1. 考虑使用静态工厂方法替代构造方法

EffectiveJava

考虑用静态工厂方法代替构造器的场景

Effective Java大厂实战之考虑以静态工厂方法代替构造方法

Effective Java大厂实战之考虑以静态工厂方法代替构造方法

Effective Java大厂实战之考虑以静态工厂方法代替构造方法