使用静态工厂方法而不是构造器

注意:静态工厂方法不是设计模式中的工厂方法。

一个类向客户端提供静态工厂方法有如下好处:

  • 有名称,不用根据参数类型和顺序区分重载方法,让代码更易读
  • 是否每次调用都需要新对象是可控制的,对于不可修改的对象可以采取缓存对象来提高性能,例如可以使用==来判断对象是否相等,而不使用equals,可以提高性能。
  • 工厂方法体内可返回返回类型的任何子类型,这在选择返回对象的类型上有很大的灵活性。一个对外隐蔽了实现类(即所返回的对象的类型对外是不可见的)的API是非常紧凑的API。这种技巧本身把自己引导成了基于接口的框架(接口为静态工厂方法提供返回类型)。接口不能有静态方法,因此按惯例返回类型为接口Type的静态工厂方法会放在一个不可实例化的类Types中:例如Java集合框架中的Collections有32个对集合接口的非常方便的实现,分别提供不可修改集合、同步集合等等,几乎所有这些实现都是通过一个不可实例化的类(java.util.Collections)的静态工厂方法导出,这些工厂所返回的类都是非public的。当前集合框架API比导出32个public类要更小,不单只在API的代码量上得到减少,同时还在概念的重量级方面。用户一眼就知道所返回的对象正如接口所描述的一样,不需要读取额外的类实现文档。另外,使用这样一种静态工厂方法需要客户端使用接口来引用这个工厂方法所返回的对象,而不是使用实现类引用,这是一个好的实践。不只由静态工厂返回的对象的类型可以是非public的,根据传入静态工厂的参数的不同,这个返回对象的类也是可变的,只要是静态工厂方法声明的返回值类型的子类型就都是允许的。静态工厂返回值类型也是可以在各发布版间变化的,以增强可维护性和性能。Java1.5引入的java.util.EnumSet没有public构造器,只有静态工厂,这些静态工厂根据底层枚举类型的大小返回两个实现中的一个:如果元素小于等于64,静态工厂返回一个RegularEnumSet实例,它由单个long支持; 如果枚举类型有大于64个元素,静态工厂会返回一个JumboEnumSet实例,它由一个long数组支持。这两个实现类对客户端是不可见的。

     由静态工厂方法返回的对象的类甚至不必在编写静态工厂方法所在的类时就存在。静态工厂的这种灵活性是服务提供者框架(service provider framework)的基石,例如JDBC。服务提供者框架是一个系统,在这个系统中,服务提供者实现服务,这个系统让这些实现对客户端可用,从而把客户端与实现分离。

     服务提供者框架有三个基本的组件:由提供者实现的服务接口、系统用于注册服务实现并让其对客户端可用的提供者注册API、客户端用于获取服务实现的服务访问API。服务访问API通常允许让客户端指定某些条件以便选择一个提供者,但客户端不是必须要指定,如果没有指定,API会返回一个默认实现。服务访问API就是构成服务提供者框架的基石的灵活静态工厂。

    服务提供者接口有一个可选的组件:服务提供者接口,由提供者用于创建他们自己的服务实现的实例。在没有服务提供者接口的情况下,实现是通过类名进行注册,并通过反射进行实例化。在JDBC中,Connection就是服务接口,DriverManager.registerDriver()就是服务提供者注册接口,DriverManager.getConnection()是服务访问接口,Driver是服务提供者接口。

    现在有许多服务提供者框架模式的变种,例如,通过使用适配器,服务访问API可以返回比提供者所需要的服务接口更加丰富的服务接口,下面是一个服务提供者接口及其一个默认实现:

// Service provider framework sketch
// Service interface
public interface Service {
    ... // Service-specific methods go here
}
// Service provider interface
public interface Provider {
    Service newService();
}
// Noninstantiable class for service registration and access
public class Services {
    private Services() { }  // Prevents instantiation (Item 4)
    // Maps service names to services
    private static final Map<String, Provider> providers =
        new ConcurrentHashMap<String, Provider>();
    public static final String DEFAULT_PROVIDER_NAME = "<def>";
// 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);
    }
// 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(
                "No provider registered with name: " + name);
        return p.newService();
    }
}

  • 减少创建参数化类型实现时的冗余信息

没有使用静态工厂时:

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();

遗憾的是,标准的集合类实现中,例如HashMap并没有类似上面定义的工厂方法。但可以把这种方法放到自己的工具类中,更加重要的是,可以在自己的参数化类中提供这种静态工厂方法。

 

只提供静态工厂方法的类的主要缺点在于不能被子类化,因为没有public或protected的构造器的类是不能被子类化的。

由public权限的静态工厂返回的非public类也是不能被子类化的,例如,不能子类化Collections里面的实现类,这可以说是因祸得福,因此这样可以促进程序员使用组合而不是继承。

另一个缺点在于静态工厂方法不易与其它静态方法区分开。

主要是因为静态工厂方法不像构造器那样明显地出现在API文档中,因此很难知道如何使用类中提供的静态工厂来代替构造方法实例化对象。可以通过在类或接口中写注释来说明,并且让静态工厂方法名遵循约定:

valueOf、of、getInstance、newInstance、getType、newType

总之,静态工厂和构造方法各有优缺点,但一向在使用构造器之前优先考虑使用静态工厂。

posted @ 2016-04-17 18:00  Mark.Chan  阅读(1527)  评论(0编辑  收藏  举报