本文的静态工厂方法
与设计模式的工厂方法模式
完全不同,要注意区分!
通常来说,我们创建一个类对象,直接使用new 调用其构造方法进行创建。但是静态工厂方法提供一个公共静态工厂方法,它是一个返回类实例的静态方法。
比如,获取一个People实例:
public static People getInstance() {
return new People();
}
而不是通过调用构造方法获取People实例(此处只是举个静态工厂方法的例子,实际上有诸多用法)。
区别于构造方法,静态工厂方法是有名字的
,正因为它是一个方法,它可以给方法取任意的方法名。
有的类中构造方法有很多,如果不仔细描述构造方法中的参数含义,其创建的对象很难理解它们有什么不同的含义。
在一个类中需要具有相同签名的多个构造方法的情况下,用静态工厂方法替换构造方法,并仔细选择名称来突出它们的差异,创建对象时很容易就知道其方法的含义了。
BigInteger有众多的构造方法,单看这些构造方法,并不能很清晰的知道其具体的含义,需要翻遍注释慢慢去了解。
而使用静态工厂方法,可以根据其语义,很轻松知道该方法会返回一个什么对象。
例如BigInteger(int, int, Random)返回一个素数,但调用者很难理解API设计者所要想表达的意思,如果此时有BigInteger.probablePrime静态工厂方法,则能一目了然的清楚API设计者所要想表达的含义。
Java线程池ThreadPoolExecutor类共有七大参数,如果仅仅使用构造方法来创建线程池,恐怕对于一个新人程序员来说这是一个噩梦。
Executors类定义了newFixedThreadPool、newFixedThreadPool等等诸多静态工厂方法,使用寥寥几个参数就可以获取一个特定功能的线程池,因为它们有“名字”,并且做了一些封装与默认值,所有就较为清晰的明白API的含义。
Date d = Date.from(instant);
Set faceCards = EnumSet.of(JACK, QUEEN, KING);
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
StackWalker luke = StackWalker.getInstance(options);
Object newArray = Array.newInstance(classObject, arrayLen);
FileStore fs = Files.getFileStore(path);
Buw eredReader br = Files.newBuw eredReader(path);
List litany = Collections.list(legacyLitany);
使用构造方法每次都会在内存中创建一个新对象,而静态工厂方法可以允许对象提前缓存或重用。
如果经常请求等价对象,或者某些对象创建时需要消耗极大的代价,它可以极大地提高性能。
这种技术常用于Flyweight(享元) 模式
或者Singleton (单例)模式
。
设计模式之【享元模式】,共享单车火起来并不是没有原因的
设计模式之【单例模式】全解,单例模式实现方式,暴力打破单例模式与解决方案,你真的认识单例模式吗?
比如说一个最简单的饿汉式单例:
public class Instance() {
private static Instance instance = new Instance();
private Instance(){}
public static Instance getInstance() {
return instance;
}
}
注意!静态工厂方法返回相同的对象,需要在任何时候保持对该对象实例的严格控制,这样做的类也叫实例控制( instance-controlled)。这也很好理解,因为如果该对象使用享元模式进行共享,修改了对象的属性及内容,所有引用该对象的地方都会感知到这一事件。
Boolean的valueOf方法就是基于这种思想,提前将True和False进行缓存,每次获取的都是相同的实例,这避免了每次通过构造方法创建新的对象,极大的提高了性能。
同时,其构造方法,在jdk9就已经弃用了。
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
@IntrinsicCandidate
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
@Deprecated(since="9", forRemoval = true)
public Boolean(boolean value) {
this.value = value;
}
这种方式在选择返回对象的类时提供了很大的灵活性。这种技术适用于基于接口
的框架,其中接口为静态工厂方法提供自然返回类型。
比如说Collections工具类,List list = Collections.synchronizedList(new ArrayList())
,这与装饰者模式
非常像:
设计模式之【装饰者模式】,实现“穿衣打扮”自由原来这么简单
Collections工具类中提供了大量的包装类。
比如说,synchronizedList,将传入的参数进行包装,将所有的方法都加上synchronized确保线程安全,这相当于装饰者模式
的一种落地实现:
这个优点与上一个优点类似,返回的类动态变化,一定是需要继承与本类或者实现了与本类相同的接口。
EnumSet的noneOf它们根据底层枚举类型的大小返回两个子类中的一个的实例:如果大多数枚举类型具有 64 个或更少的元素,静态工厂将返回一个RegularEnumSet 实例, 返回一个 long 类型;如果枚举类型具有六十五个或更多元素,则工厂将返回一个JumboEnumSet 实例,返回一个 long 类型的数组。
这两个实现类的存在对于客户是不可见的。 如果 RegularEnumSet 不再为小枚举类型提供性能优势,则可以在未来版本中将其淘汰,而不会产生任何不良影响。 同样,未来的版本可能会添加 EnumSet 的第三个或第四个实现,如果它证明有利于性能。 客户既不知道也不关心他们从工厂返回的对象的类别; 他们只关心它是 EnumSet 的一些子类。
这种灵活的静态工厂方法构成了服务提供者框架的基础,比如 Java 数据库连接 API(JDBC)。服务提供者框架是提供者实现服务的系统,并且系统使得实现对客户端可用,从而将客户端从实现中分离出来。
例如DriverManager.getConnection
,获取的连接mysql、oracle是根据Driver驱动进行动态连接的。JDK只提供了DriverManager进行jdbc管理,而客户端需要各大厂商自行实现,实现了客户端与服务提供者进行分离。
同理,slf4j日志门面也是如此,门面和具体的实现分离,各自进行维护。
这也类似于门面模式
的一种落地。
虽然静态工厂方法有诸多优点,但是要结合实际应用场景。
如果只有静态工厂方法而没有公共或受保护构造方法,这将导致该类无法提供子类
。例如,在 Collections框架中不可能将任何方便实现类子类化。可以说,这可能是因祸得福,因为它鼓励程序员使用组合而不是继承,并且是不可变类型(final)。
另外,静态工厂方法在API文档上,和普通静态方法混杂在一起,因此要想区分静态工厂方法和普通静态方法,或许要花费一定的精力。所以,静态工厂方法的命名很有讲究(参考本文静态工厂方法的命名)。