Effective Java:Ch2_创建销毁对象:Item1_考虑用工厂方法替代构造函数

本章的主题是创建和销毁对象:何时创建、怎样创建;何时应该避免创建、如何避免创建;如何确保对象适时被销毁;如何管理对象销毁前的清理动作。


一个类如果要允许客户获得其实例,常用方法是提供一个public的构造函数。还有另外一个方法,也应该在每个程序员的工具集中占有一席之地:类可以提供一个public的静态工厂方法,这个方法返回类的实例。【例】下面是Boolean类(基本类型boolean对应的包装类)代码里的一个简单示例,该方法将一个boolean简单类型转换为一个Boolean对象引用。

public static Boolean valueOf(boolean b){
    return b ? Boolean.TRUE : Boolean.FALSE;
}
注意这里讲的静态工厂方法与设计模式中的工厂方法模式不一样的,它在设计模式中没有直接等价物。

类可以提供给它的客户静态工厂方法,用来替代构造方法,或作为构造方法的补充。用静态工厂方法来替代public构造方法既有优点也有缺点。


优点一:静态工厂方法不同于构造函数,他们有名称如果构造函数的参数不能确切地描述正被返回对象,那么具有合适名称的静态工厂方法就更易用,对应的客户端代码页更加易于阅读。【例】例如,构造方法BigInteger(int, int, Random)返回一个可能为素数的BigInteger,而用一个名为BigInteger.probablePrime()的静态工厂方法就更好。(JDK1.4最终增加了这个方法。)

public class BigInteger extends Number implements Comparable<BigInteger> {

   public static BigInteger probablePrime(int bitLength, Random rnd) {
        if (bitLength < 2)
            throw new ArithmeticException("bitLength < 2");

        // The cutoff of 95 was chosen empirically for best performance
        return (bitLength < SMALL_PRIME_THRESHOLD ?
                smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) :
                largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd));
    }

    public BigInteger(int bitLength, int certainty, Random rnd) {
        BigInteger prime;

        if (bitLength < 2)
            throw new ArithmeticException("bitLength < 2");
        // The cutoff of 95 was chosen empirically for best performance
        prime = (bitLength < 95 ? smallPrime(bitLength, certainty, rnd)
                                : largePrime(bitLength, certainty, rnd));
        signum = 1;
        mag = prime.mag;
    }

}
对于指定的方法签名,一个类只能有一个对应的构造函数。程序员通常这样来避开这个限制:可以提供两个构造函数,他们的参数列表仅仅是参数类型的顺序不同。这实在是一个坏主意。API的用户将不会记得这些构造函数哪个是哪个,常会无意地调用错误的构造函数。其他人读到使用这些构造函数的代码时,将不会知道这些代码是做什么的,除非参考类文档。

由于静态工厂方法有名称,所以没有上述限制。假如一个类需要方法签名相同的多个构造函数,就用静态工厂方法来替代构造函数,并仔细地选择方法名称以便突出它们之间的区别。


优点二:静态工厂方法不同于构造函数,无需每次被调用时都创建一个新对象这使得非可变类(Item15)可以使用预先创建的实例,或者在创建实例时缓存起来,之后分发给客户,从而避免创建不必要的重复对象。Boolean.valueOf(boolean)方法演示了这种技术:它从不创建对象。这种技术类似于Flyweight模式如果客户端经常请求创建相同的对象,那这种技术能极大的提高性能,尤其是当创建对象开销很大时。

静态工厂方法可以再反复调用时返回同一个对象,这使得类可以严格控制在哪个时刻有哪些实例存在。这种类又被称为实例受控的类instance-controlled)。编写实例受控的类有这么几个理由:第一,实例受控允许一个类确保是单例的(Item3),或者是不可实例化的(Item4);同时,实例受控允许非可变类(Item15)确保不会存在两个相等的实例,即当且仅当a==b时才有a.equals(b),如果一个类做出了这种保证,则它的客户端可以用==操作符来地带equals(Object)方法,这可能会提高性能。枚举类型(Item30)就保证了这一点。


优点三:静态工厂方法不同于构造函数,它能返回任意子类型的对象这让你在选择返回对象的类型时有了很大的灵活性。

这种灵活性的一个应用是,API可以返回一个对象,而无需使对象的类public。用这种方式隐藏实现类能够产生一个非常紧凑的API。这种技术适用于基于接口的框架interface based frameworks, Item18),在这种框架中,接口成为静态工厂方法的自然返回类型。由于接口不能有静态方法,所以按照惯例,接口Type的静态工厂方法被放在一个名为Types的不可实例化类noninstantiable class, Item4)中

【例】例如,Java集合框架中有32个集合接口的便利实现,提供不可修改的集合、同步集合等等。几乎所有的实现都通过一个不可实例化类(java.util.Collections)中的静态工厂方法导出,返回对象的类都是非public的。——Collections.unmodifiableMap(Map)方法的返回类型是UnmodifiableMap,是个private类!

public class Collections {
 public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
 return new UnmodifiableMap<K,V>(m);
 }

 private static class UnmodifiableMap<K,V> implements Map<K,V>, Serializable {
    }
}
如果把上述便利实现都作为public类导出,那集合框架API会臃肿很多。而且这并不仅仅是API数量的减少,还有conceptual weight的减轻。用户知道返回的对象都是由API通过接口精确定义了的,所以无需为实现类阅读额外的类文档。此外,使用静态工厂方法后,要求客户端通过接口来引用返回对象,而不是通过实现类来引用返回对象,这通常是一个好习惯。


public静态工厂方法返回的对象类型不仅是可以非public,而且还能根据参数的不同,而返回不同的类型。只要是所声明的返回类型的任何子类型,都是允许的。为了提高软件可维护性和性能,返回类型还可以随着版本的不同而不同。

【例】JDK1.5版本引入的java.util.EnumSet类没有public构造函数,只有静态工厂方法。根据底层枚举类型的大小,这些工厂方法可以返回两种实现:如果拥有64个或更少的元素(大多数枚举类型都是这样),静态工厂方法返回一个RegularEnumSet实例,用单个long来支持;如果枚举类型拥有65个或更多的元素,静态工厂方法则返回JumboEnumSet实例,用long数组来支持。

这两种实现类的存在对客户端是不可见的。如果RegularEnumSet对于小的枚举类型提供性能优势,那么就能在以后的版本中删掉,并不产生副作用。同样,如果可以提高性能,以后版本可以增加EnumSet的第三或第四个实现。客户端不知道也不关心静态工厂返回的对象的类型,客户端只关心它是EnumSet的某个子类。

public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
 implements Cloneable, java.io.Serializable
{
    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

        if (universe.length <= 64)
            return new RegularEnumSet<E>(elementType, universe);
        else
            return new JumboEnumSet<E>(elementType, universe);
    }
}

class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
} 
class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {
}


在编写静态工厂方法时,其返回的对象所属的类甚至可以不存在。静态工厂方法的这种灵活性形成了服务提供者框架service provider frameworks)的基础,例如Java数据库连接API(JDBC)。所谓服务提供者框架是这样一个系统:在这个系统中多个服务提供者实现一个服务,系统让这些实现对客户端可用,使他们与实现解耦。

服务提供者框架中有三个重要组件:服务接口(service interface),它负责提供实现;提供者注册API(provider registration API),系统用它来注册实现,让客户端访问实现;服务访问API(service access API),客户端用它来获取服务的一个实例,它一般允许客户端指定选择提供者的条件,但并不强制要求。如果不指定,则API返回一个默认实现的实例。服务访问API是“灵活的静态工厂”,形成了服务提供者框架的基础。

服务提供者框架还有第四个可选组件:服务提供者接口(service provider interface),提供者实现它用来创建其服务实现的实例。如果没有服务提供者接口,实现则是通过类名来注册,并通过反射实例化(Item53)。【例】以JDBC为例,Connection就是服务接口(service interface);DriverManager.registerDriver是提供者注册API(provider registration API);DriverManager.getConnection是服务访问API(service access API);Driver是服务提供者接口(service provider interface)。

服务提供者框架有多种变体。例如服务访问API可以通过适配器模式,返回比提供者要求的更加丰富的服务接口。【例】下面是一个简单示例,有一个服务提供者接口,和一个默认提供者:

//Service provider framework sketch

//--1)Service interface
public interface Service{
	//service-specific methods go here
}

//--4)Service provider interface
public interface Provider{
	Service newService();
}

//Noninstantiable class for service registration and access
public class Services{
	private Services(){} //pervents instantiation
	
	//maps service names to services
	private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
	public static final String DEFAULT_PROVIDER_NAME = "<def>";
	
	//--2)provider registration API
	public static void registerDefaultProvider(Provider p){
		registerProvider(DEFAULT_PROVIDER_NAME, p);
	}
	public static void registerProvider(String name, Provider p){
		providers.put(name, p);
	}
	
	//--3)service access API:静态工厂方法
	public static Service newInstance(){
		return newInstance(DEFAULT_PROVIDER_NAME);
	}
	public static Service newInstance(String name){
		Provider p = providers.get(name);
		if(p == null){
			throw new IllegalArgumentException();
		}
		return p.newService();
	}
}

优点四:静态工厂方法创建参数化类型实例时更加简洁如果你调用参数化类的构造函数,那么很不幸,你必须要指定类型参数,即便上下文中已明确了类型参数。这通常要求你连续两次提供类型参数:

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

随着类型参数长度和复杂性的不断增长,这种冗长的规范很快变得痛苦起来。然而,使用静态工厂的话,编译器能够替你计算出类型参数。这就是类型推导type inference)。【例】例如,假设HashMap提供了如下静态工厂:

public static <K, V> HashMap<K, V> newInstance(){
    return new HashMap<K, V>();
}

然后你就可以讲上文冗长的声明替换为如下这种简洁的形式:

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

也许有一天Java可以再构造函数调用和方法调用时进行这种类型推导,不过截至JDK1.6还不能做到。

遗憾的是,JDK1.6版本的标准集合实现(如HashMap)中并没有工厂方法,不过你可以再自己的工具类中加入这些方法。更重要的是,你可以再自己的参数类类中提供这种静态工厂。


缺点一:只提供静态工厂方法的主要缺点是,没有public或protected构造函数的类不能被子类化。public静态工厂返回的非public类也同样如此。例如,你不能子类化集合框架中便利实现类。这样也好,它鼓励程序员使用复合,而不是继承(Item16)。


缺点二:静态工厂方法不容易与其他静态方法相区分。它们并不像构造函数在API文档中那样突出,所以如果类提供静态工厂方法而不是构造函数,要找出如何实例化该类就比较困难了。也许某一天Javadoc工具会注意到静态工厂方法。现阶段,你可以通过在类或接口注释中标注静态工厂,并遵守通用的命名习惯,来减少这一缺陷。静态工厂方法的一些常用命名如下:

valueOf——不严格地讲,该方法返回一个与其方法参数具有相同值的实例。这种静态工厂实际上是类型转换方法。

of——valueOf的简洁形式,EnumSet(Item32)使之流行起来。

getInstance——返回方法参数所描述的一个实例,不过不一定与方法参数具有相同值。在单例模式中,getInstance没有参数,返回唯一实例。

newInstance——与getInstance类似,不过newInstance确保返回的每个实例都是不同的。

getType——与getInstance类似,只不过当工厂方法在另外一个不同的类中的时候使用。Type表示工厂方法返回对象的类型。

newType——与newInstance类似,只不过当工厂方法在另外一个不同的类中的时候使用。Type表示工厂方法返回对象的类型。


总之,静态工厂方法和public构造方法各有用处,我们需要理解他们各自的长处。通常静态工厂更可取,所以第一反应应该是考虑使用静态工厂,而不是public构造函数。







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值