用静态工厂方法代替构造器

参考于《Effective Java》

前言

对于类而言,获得其实例最常见的方式就是提供一个构造器。
如果我们不写构造器,编译器会帮我们自动加上一个被public修饰的空构造器。

除了提供构造器以外,静态工厂方法也应该被考虑到程序的设计当中。

静态工厂方法

本质上就是类的一个静态方法,返回值是类的实例对象。

通过私有化构造器,无法直接new对象,而是通过运行静态工厂方法获取对象实例。

这么做既有优势,也有劣势。应该结合实际情况来使用。

优势

1、静态工厂方法有名称

我们知道,类的构造器名称必须和类名相同。

如果类比较复杂,需要多个构造器时,往往只能在构造器的方法签名上做文章。

通过不同的参数个数,参数类型,参数顺序来编写多个构造器不是个好主意。

面对这样的API,调用者往往不知所云,到底该调用哪一个构造器。

静态工厂方法可以很好的规避这一点,通过不同的方法名来区分,调用者就会一目了然。

例子

@Data
public class Person {
	private static enum Sex{
		MAN,WOMAN
	}
	private String name;
	private Sex sex;

	private Person(){}

	public static Person createMan(){
		Person person = new Person();
		person.sex = Sex.MAN;
		return person;
	}

	public static Person createWoman(){
		Person person = new Person();
		person.sex = Sex.MAN;
		return person;
	}
}

2、不必在每次调用时都创建新对象

使用构造器创建对象,每次都会返回一个新的对象。
静态工厂方法使得对象的创建是可控的。

可以将构建好的实例缓存起来进行重复利用,从而避免创建不必要的重复对象。
如果创建对象的代价很高,可以利用这项技术极大地提升性能。

Boolean.valueOf()很好的说明了这点。
它从不创建对象,返回的永远是自身的TRUE,FALSE常量。

public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
}

还有Integer.valueOf()采用了缓存的机制。
如果数值在 IntegerCache.low 和 IntegerCache.high 之间将从缓存中返回。

public static Integer valueOf(int i) {
	if (i >= IntegerCache.low && i <= IntegerCache.high)
		return IntegerCache.cache[i + (-IntegerCache.low)];
	return new Integer(i);
}

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

构造器返回的只能是本类的对象实例。

但是静态工厂方法可以返回原返回类型的任意子类,这让我们选择返回类实例时大大的提高了灵活性

当原先的类不能满足需求,需要替换类时,只需要修改静态工厂方法,而这一切对于调用者是零感知的。

public class Parent {
	
	public void func(){
		System.out.println("Parent Function.");
	}

	public static Parent getInstance(){
		//返回任意子类实例
		return new Child();
	}
}

public class Child extends Parent {

	@Override
	public void func() {
		super.func();
		System.out.println("Child Enhanced Version.");
	}
}

4、返回的对象的类可以随着每次调用而发生变化

比起构造器只能返回该类本身,静态工厂方法显得灵活的多。
其完全是我们可控的,只要是已声明的子类型都是允许的。
返回的类对象可能随着发行版本的不同而不同。

5、返回实例的类可以在编写方法时不存在

返回的子类可以在编写静态工厂方法时不存在,依赖于JDK提供的SPI机制,可以在需要时通过配置让JVM发现服务并加载。

可以先编写工厂方法,没有实现类也无所谓。

public interface SPIInterface {
	void func();
}

public class SPI {
	public static SPIInterface getInstance(){
		ServiceLoader<SPIInterface> serviceLoader = ServiceLoader.load(SPIInterface.class);
		Iterator<SPIInterface> iterator = serviceLoader.iterator();
		if (iterator.hasNext()) {
			return iterator.next();
		}
		throw new RuntimeException("没有可用的实现类");
	}

	public static void main(String[] args) {
		SPIInterface instance = SPI.getInstance();
		instance.func();
	}
}

当需要提供服务时,按照SPI的规范来进行配置即可。

创建实现类

public class SPIImpl implements SPIInterface {

	@Override
	public void func() {
		System.out.println("SPI 实现方法....");
	}
}

SPI配置

在resources下新建文件:META-INF/services/unit1.item1.SPIInterface,内容为:

unit1.item1.SPIImpl

重新运行上面的代码即可执行服务。

劣势

1、不能被子类化

构造器私有化后,该类将无法被子类化。

2、较难以发现

相对于传统的new方式创造对象,静态工厂方式较难以被发现。
而且在JavaDoc生成的文档中也没有像构造器一样被明确标识出来。

但是可以通过标准的命名习惯来弥补这一不足。

静态工厂方法命名规范

  • form
    类型转换方法,它只有单个参数,返回该类型的一个相对应的实例。
    Dated= Date.from(instant) ;

  • of
    聚合方法,带有多个参数,返回该类型的一个实例,把它们合并起来。
    Set faceCards = EnumSet.of(JACK , QUEEN, KING];

  • valueOf
    比 from 和 of 更烦琐的一种替代方法。
    BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

  • instance/getInstance
    返回的实例是通过方法的(如有)参数来描述的,但是不能说与参数具有同样的值。
    StackWalke luke = StackWalke.getinstance(options);

  • create/newInstance
    像instance/getInstance一样,但create/newInstance能够确保每次调用都返回一个新的实例。
    Object newArray = Array.newInstance(classObject,arrayLen);

  • getType
    像getInstance一样,但是在工厂方法处于不同的类中的时候使用。
    FileStore fs = Files.getFileStore(path);

  • newType
    像newInstance一样,但是在工厂方法处于不同的类中的时候使用。
    BufferedReader br = Files.newBufferedReader(path);

  • type
    getType和newType的精简版。
    List litany = Collections.list(legacylLtany);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小潘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值