您的位置:首页 > 其它

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

2017-08-28 21:33 260 查看
这是 Effective Java 的第一节的标题。本文更多的是摘译该节的内容。

什么是静态工厂方法(static factory methods)


static factory methods 翻译过来就是静态工厂方法。它并不是 GOF 提的设计模式中的一个设计模式。

我们看下面的例子(摘自JDK 1.7)。
public final class Boolean implements java.io.Serializable,
        Comparable<Boolean> {

    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);
       
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
}


我们要获取一个 Boolean 的一个对象,可以使用构造函数 
new
Boolean(true)
 也可以使用里面的静态方法
Boolean.valueOf(true)
,后者便是静态工厂方法。


静态工厂方法的优点


和直接使用构造函数相比,静态工厂方法的优点有:

静态工厂方法在方法命名上更具有可读性

在使用构造函数去构造对象的时候,我们传递不同的参数构造不同类型的对象。如果不看文档的话,我们很难记住传递什么参数能够构造什么样子的对象。用静态的工厂方法就不一样啦,我们可以不同的工厂方法不同名字,我们就可以很容易的记住什么方法名可以构造什么样的对象。关于这一点,在 JDK 中最有说服力的一个例子就是
java.util.concurrent.Executors
,里面N多的静态工厂方法:newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool、newScheduledThreadPool等等

静态工厂方法不需要每次在被调用的时候都构造一个新的对象

也就是说我们调用静态工厂方法返回的可能是缓存的一个对象,而不是一个新的对象。这可以减少创建新的对象,从来提高性能,前面提到的 Boolean 就是一个例子。这对于 immutable 的对象特别有用,读到这里,我翻看了一下 JDK 的源代码,才发现原来 JDK 中原始数据类型(Primitive Data Types)的包装类都是 immutable 的。也就是说只要创建 Boolean、Integer、Long 等的对象,它的值就是不能改变的。我们再看 Integer 提供的静态工厂函数:

public static Integer valueOf(int i) {
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}


上面的代码把出现概率高的 int 做了一个 cache,这样每次只要返回 cache 里的对象,而不用新建一个对象。

静态工厂方法还可以返回该类型的子类对象

这个特性让静态工厂方法的可扩展性大大的优于构造函数。在 JDK 中最典型的应该是 java.util.EnumSet。EnumSet 本身是 absract,我们无法直接调用它的构造函数。不过我们可以调用它的静态方法 noneOf 来创建对象,RegularEnumSet/JumboEnumSet 都继承至 EnumSet,noneOf 根据参数返回合适的对象。

public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
    implements Cloneable, java.io.Serializable {

    EnumSet(Class<E>elementType, Enum[] universe) {
    }

    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }
}


静态工厂方法还可以简化参数化类型的对象创建

这个优点优点语法糖的味道,不过语法糖人人都喜欢啦。

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

public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
}
Map<String, List<String>> m = HashMap.newInstance();


第一行冗长的代码我们就可以简化成第三行的代码。比较典型的例子就是 guava 中的 Maps (Google
collections library)


静态工厂方法的缺点


静态工厂方法当然也有其缺点。

如果我们在一个类中将构造函数设为private,只提供静态工厂方法来创建对象,那么我们就不能通过继承的方式来扩展该类。不过还好的是,在需要进行扩展的时候,我们现在一般提倡用组合而不是继承。

第二个缺点是,静态构造方法不能和其他的静态方法很方便的区分开来。好吧,原文的意思是静态构造方法做的是一等公民(构造函数)的事,却得不到一等公民的待遇。

为了和普通函数有所区分,原文建议在命名静态工厂方法的时候遵循一定的规则

valueOf — 返回和参数一样的对象,通常都用作类型转换,比如 Intger.valueOf(int i)

of — 和 valueOf 类似。

getInstance — 根据参数返回对应的对象,该对象可能是缓存在对象池中的对象。对于单例 singleton,我们使用无参数的 getInstance,并且总是返回同一个对象

newInstance — 和 getInstance 一样,不过这个方法的调用每次返回的都是新的对象。

getType — 和 getInstance 类似,不过区别是这个方法返回的对象是另外一个不同的类。

newType — 和 getType 类似,不过每次返回的都是一个新的对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: