写出优美的代码:考虑使用静态工厂方法替代构造方法

文章目录

一、静态工厂方法区别于工厂方法模式

本文的静态工厂方法与设计模式的工厂方法模式完全不同,要注意区分!

通常来说,我们创建一个类对象,直接使用new 调用其构造方法进行创建。但是静态工厂方法提供一个公共静态工厂方法,它是一个返回类实例的静态方法。

比如,获取一个People实例:

java 复制代码
public static People getInstance() {
	return new People();
}

而不是通过调用构造方法获取People实例(此处只是举个静态工厂方法的例子,实际上有诸多用法)。

二、静态工厂方法的优点

1、有名字

(1)优势

区别于构造方法,静态工厂方法是有名字的,正因为它是一个方法,它可以给方法取任意的方法名。

有的类中构造方法有很多,如果不仔细描述构造方法中的参数含义,其创建的对象很难理解它们有什么不同的含义。

在一个类中需要具有相同签名的多个构造方法的情况下,用静态工厂方法替换构造方法,并仔细选择名称来突出它们的差异,创建对象时很容易就知道其方法的含义了。

(2)源码分析:BigInteger

BigInteger有众多的构造方法,单看这些构造方法,并不能很清晰的知道其具体的含义,需要翻遍注释慢慢去了解。

而使用静态工厂方法,可以根据其语义,很轻松知道该方法会返回一个什么对象。

例如BigInteger(int, int, Random)返回一个素数,但调用者很难理解API设计者所要想表达的意思,如果此时有BigInteger.probablePrime静态工厂方法,则能一目了然的清楚API设计者所要想表达的含义。

(3)源码分析:Executors

Java线程池ThreadPoolExecutor类共有七大参数,如果仅仅使用构造方法来创建线程池,恐怕对于一个新人程序员来说这是一个噩梦。

Executors类定义了newFixedThreadPool、newFixedThreadPool等等诸多静态工厂方法,使用寥寥几个参数就可以获取一个特定功能的线程池,因为它们有"名字",并且做了一些封装与默认值,所有就较为清晰的明白API的含义。

(4)常用命名 名称

  • from------A 类型转换方法,它接受单个参数并返回此类型的相应实例,例如:Date d = Date.from(instant);
  • of------一个聚合方法,接受多个参数并返回该类型的实例,并把他们合并在一起,例如:Set faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf------from 和 to 更为详细的替代 方式,例如:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • instance 或 getinstance------返回一个由其参数 (如果有的话) 描述的实例,但不能说它具有相同的值,例如:StackWalker luke = StackWalker.getInstance(options);
  • create 或 newInstance------与 instance 或 getInstance 类似,除了该方法保证每个调用返回一个新的实例,例如:Object newArray = Array.newInstance(classObject, arrayLen);
  • getType------与 getInstance 类似,但是如果在工厂方法中不同的类中使用。Type 是工厂方法返回的对象类型,例如:FileStore fs = Files.getFileStore(path);
  • newType------与 newInstance 类似,但是如果在工厂方法中不同的类中使用。Type 是工厂方法返回的对象类型,例如:Buw eredReader br = Files.newBuw eredReader(path);
  • type------ getType 和 newType 简洁的替代方式,例如:List litany = Collections.list(legacyLitany);
  • 其他------根据方法的具体逻辑,简单描述方法的功能

2、不需要每次调用时都创建一个新对象

(1)优势

使用构造方法每次都会在内存中创建一个新对象,而静态工厂方法可以允许对象提前缓存或重用。

如果经常请求等价对象,或者某些对象创建时需要消耗极大的代价,它可以极大地提高性能。

这种技术常用于Flyweight(享元) 模式或者Singleton (单例)模式
设计模式之【享元模式】,共享单车火起来并不是没有原因的
设计模式之【单例模式】全解,单例模式实现方式,暴力打破单例模式与解决方案,你真的认识单例模式吗?

比如说一个最简单的饿汉式单例:

java 复制代码
public class Instance() {
	private static Instance instance = new Instance();
	private Instance(){}
	public static Instance getInstance() {
		return instance;
	}
}

注意!静态工厂方法返回相同的对象,需要在任何时候保持对该对象实例的严格控制,这样做的类也叫实例控制( instance-controlled)。这也很好理解,因为如果该对象使用享元模式进行共享,修改了对象的属性及内容,所有引用该对象的地方都会感知到这一事件。

(2)源码分析:Boolean

Boolean的valueOf方法就是基于这种思想,提前将True和False进行缓存,每次获取的都是相同的实例,这避免了每次通过构造方法创建新的对象,极大的提高了性能。

同时,其构造方法,在jdk9就已经弃用了。

java 复制代码
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;
}

3、可以返回原返回类型的任何子类型

(1)优势

这种方式在选择返回对象的类时提供了很大的灵活性。这种技术适用于基于接口的框架,其中接口为静态工厂方法提供自然返回类型。

比如说Collections工具类,List list = Collections.synchronizedList(new ArrayList()) ,这与装饰者模式非常像:
设计模式之【装饰者模式】,实现"穿衣打扮"自由原来这么简单

(2)源码分析:Collections

Collections工具类中提供了大量的包装类。

比如说,synchronizedList,将传入的参数进行包装,将所有的方法都加上synchronized确保线程安全,这相当于装饰者模式的一种落地实现:

4、返回对象的类可以根据输入参数的不同而不同

(1)优势

这个优点与上一个优点类似,返回的类动态变化,一定是需要继承与本类或者实现了与本类相同的接口。

(2)源码分析:EnumSet

EnumSet的noneOf它们根据底层枚举类型的大小返回两个子类中的一个的实例:如果大多数枚举类型具有 64 个或更少的元素,静态工厂将返回一个RegularEnumSet 实例, 返回一个 long 类型;如果枚举类型具有六十五个或更多元素,则工厂将返回一个JumboEnumSet 实例,返回一个 long 类型的数组。

这两个实现类的存在对于客户是不可见的。 如果 RegularEnumSet 不再为小枚举类型提供性能优势,则可以在未来版本中将其淘汰,而不会产生任何不良影响。 同样,未来的版本可能会添加 EnumSet 的第三个或第四个实现,如果它证明有利于性能。 客户既不知道也不关心他们从工厂返回的对象的类别; 他们只关心它是 EnumSet 的一些子类。

5、编写包含该方法的类时,返回的对象的类不需要存在

这种灵活的静态工厂方法构成了服务提供者框架的基础,比如 Java 数据库连接 API(JDBC)。服务提供者框架是提供者实现服务的系统,并且系统使得实现对客户端可用,从而将客户端从实现中分离出来。

例如DriverManager.getConnection,获取的连接mysql、oracle是根据Driver驱动进行动态连接的。JDK只提供了DriverManager进行jdbc管理,而客户端需要各大厂商自行实现,实现了客户端与服务提供者进行分离。

同理,slf4j日志门面也是如此,门面和具体的实现分离,各自进行维护。

这也类似于门面模式的一种落地。

三、总结

虽然静态工厂方法有诸多优点,但是要结合实际应用场景。

如果只有静态工厂方法而没有公共或受保护构造方法,这将导致该类无法提供子类。例如,在 Collections框架中不可能将任何方便实现类子类化。可以说,这可能是因祸得福,因为它鼓励程序员使用组合而不是继承,并且是不可变类型(final)。

另外,静态工厂方法在API文档上,和普通静态方法混杂在一起,因此要想区分静态工厂方法和普通静态方法,或许要花费一定的精力。所以,静态工厂方法的命名很有讲究(参考本文静态工厂方法的命名)。

相关推荐
吾与谁归in4 分钟前
【C#设计模式(13)——代理模式(Proxy Pattern)】
设计模式·c#·代理模式
吾与谁归in6 分钟前
【C#设计模式(14)——责任链模式( Chain-of-responsibility Pattern)】
设计模式·c#·责任链模式
闲人一枚(学习中)13 分钟前
设计模式-创建型-原型模式
设计模式
Iced_Sheep44 分钟前
干掉 if else 之策略模式
后端·设计模式
哪 吒8 小时前
最简单的设计模式,抽象工厂模式,是否属于过度设计?
设计模式·抽象工厂模式
Theodore_10228 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
转世成为计算机大神11 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
小乖兽技术12 小时前
23种设计模式速记法
设计模式
小白不太白95014 小时前
设计模式之 外观模式
microsoft·设计模式·外观模式
小白不太白95014 小时前
设计模式之 原型模式
设计模式·原型模式