什么是静态工厂方法?
既然要用静态工厂方法代替构造器,那首先得知道静态工厂方法是什么,有什么优势。静态工厂方法的定义:不通过 new,而是用一个静态方法来对外提供自身实例的方法,即为我们所说的静态工厂方法(Static factory method)。
这里说的静态工厂方法不是设计模式中的工厂模式,而是通过一个静态的方法返回类的实例,这个大家肯定都用过,单例就是一个例子。
下面是Boolean类型的示例:
/**
* The {@code Boolean} object corresponding to the primitive
* value {@code true}.
*/
public static final Boolean TRUE = new Boolean(true);
/**
* The {@code Boolean} object corresponding to the primitive
* value {@code false}.
*/
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
静态工厂方法优势
第一大优势静态工厂方法有方法名。通过方法名可以较为准确地描述返回的对象实例。另外,构造器在参数相同的情况下只能通过参数顺序的不同来区分,这种构造器非常容易混淆,静态工厂方法可以有效地避免这种麻烦。
第二大优势在于不必创建一个新的对象。这个优势在某些情况下极为明显,还是拿Boolean类型来说明,Boolean.valueof(boolean)从不创建对象,Boolean.TRUE和Boolean.FALSE都是final的,它们只在初始化时创建一个实例,以后每次调用valueof方法都返回相同的实例。可以设想,如果每次都创建一个新的Bolean实例,在频繁使用的情况下将造成多大的内存浪费。
Calendar calendar = Calendar.getInstance();
在实际的场景中,单例的写法也大都是用静态工厂方法来实现的。
第三大优势在于可以返回原返回类型的子类。用户只需要通过接口来引用被返回的对象,而不需要关注具体的实现类。这里原书中举了三个例子,这三个例子从不同的角度描述了这种方式的优势。第一个是例子是java.util.Collections,在它的内部有很多Collection的非公有实现类(包括不可修改的集合、同步集合),这些实现类都通过静态工厂方法导出。这极大地减少了API的数量,用户不需要在一大堆的API文档前做选择了。第二个例子是java.util.EnumSet,它根据底层枚举类型的大小来返回不同的实现类,这么做的好处是显而易见的,在它的后续版本中假如需要增加或者删除一些实现类对于它的使用者是没有任何影响的。第三个例子就是服务提供者框架,最典型的应用就是JDBC。先来了解一下服务提供者框架的三个重要的组件:服务接口(Service Interface),这个是由提供者来实现的;提供者注册API(Provider Registration API),这是给服务提供者注册的接口;服务访问接口(Service Access API),是用户用来获取服务实例的。对于JDBC来说,Connection就是服务接口,DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API。这个框架的好处就是在编写时不需要先有服务提供者的具体实现。
第四大优势在于在创建参数化类型实例的时候,代码更加简洁。下面看一个对比:
方式1:Map<String,List<String>> m = new HashMap<String,List<String>>();
方式2:Map<String,List<String>> m = HashMap.newInstance();
你更喜欢哪种?毫无疑问是第2种方式,虽然这个例子的类型参数还不足够复杂。HashMap目前还没有提供这个静态工厂方法。
静态工厂方法的劣势
静态工厂方法的主要缺点在于,类如果不含公有的或受保护的构造器,就不能被子类化。它鼓励使用复合,而不是继承。如果你的类设计就是用来继承的,那就不能把构造器设置成私有的了。
第二个缺点在于与其他的静态方法实际上没有任何区别。这个只是在API中不能明确地知道哪个是静态工厂方法,一般静态工厂方法都使用下面一些惯用的名称:valueof、of、getInstance、newInstance、getType、newType.构造器和静态工厂方法各有利弊,在设计一个类的时候尽量优先考虑静态工厂。