第1条 考虑用静态工厂方法代替构造器

1. 什么是静态工厂方法

对于类而言,为了让客户端获取它自身的一个实例,最常用的方法就是提供一个公有的构造方法。
但其实还有一种方法–类提供一个公有静态工厂方法(static factory method),它只是一个返回类的实例的静态方法。如下代码所示,是来自Boolean的静态工厂方法、将boolean基本类型值转换成一个Boolean对象引用:

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

注意:类的静态工厂方法不同于设计模式的工厂设计模式。本条规约跟设计模式没有关联

2. 静态工厂方法的优势

1)它们有名称,可读性更好

//使用构造方法,返回一个BigInteger随机素数;
BigInteger(int , int , Random)
//更好的表达方式  
public static BigInteger probablePrime();

由于语言的特性,一个类只能有一个带有指定签名的构造器(即Java 的构造函数都是跟类名一样的)。这导致的一个问题是构造函数的名称不够灵活,经常不能准确地描述返回值,遇到有多个重载的构造函数时尤其突出,如果参数类型、数目又比较相似的话,如果没有参考类的文档,往往很容易调用错误的构造函数。比如Date类(目前新的Java版本只保留了一个无参和一个有参的构造函数,其他的都已标记为Deprecated):

Date date0 = new Date();
Date date1 = new Date(0L);
Date date2 = new Date("0");
Date date3 = new Date(1,2,1);
Date date4 = new Date(1,2,1,1,1);
Date date5 = new Date(1,2,1,1,1,1);

2)不需要每次调用的时候都创建新对象
对于不可变类(如Boolean,String等)可以使用预先构建好的实例,或将构建好的实例缓存起来,进行重复利用,避免创建不必要的重复对象、尤其是创建代价高的对象,节约创建对象的开销。Boolean.valueOf(boolean)就是说明了这项技术、它从来不创建对象。
在实际场景中,单例模式大都是通过静态工厂方法来实现的。https://www.jianshu.com/p/eb30a388c5fc

3)可以返回原返回类型的任何子类的对象
构造方法只能返回自身的确切的类型,而静态工厂方法可以根据需要随意的返回任何子类型。如Collections中的静态工厂方法synchronizedMap()一样,返回类型甚至在静态工厂方法所属类中是不存在的。

public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
    }

这个特性也使得面向抽象编程或面向接口编程更为灵活,静态工厂方法构成了服务提供者框架的基础,比如JDBC的API。服务提供者框架有四个重要的组件:服务接口,服务提供者接口,提供者注册API,服务访问API。对于JDBC来说,Connection就是它的服务接口,DriverManager.registerDriver是提供者注册接口,DriverManager.getConnection是服务访问API,Driver是服务提供者接口。
4) 代码更加简洁,Java 7开始已经不存在优势。
这条主要是针对带泛型类的繁琐声明的,需要重复书写两次泛型参数:

Map<String, List<String>> map = new HashMap<String, List<String>>();

而Java 7开始这种方式已经被优化过了 —— 对于一个已知类型的变量进行赋值时,由于泛型参数是可以被推导出,所以可以在创建实例时省略掉泛型参数。

Map<String, List<String>> map = new HashMap<>();

5)增大类提供者对自己提供类的控制能力,降低类使用者的成本
从类的提供者角度来看,我们其实无法控制调用者的具体行为,但是我们可以尝试使用一些方法来增大自己对类的控制力,减少调用方犯错误的机会,这也是对代码更负责的具体体现。
从类的使用者角度来看,使用者对类一般不是很熟悉,希望对类的使用成本降低,那么最简单粗暴的方式就是new一个实例,但是new出来的实例使用者往往其实是没有概念的,会导致代码比较混乱。

public class People {
	privte int type;
	
	public People(int type) {
		this.type = type;
	}
}

public enum TypeEnum {
	MAN(1),
	FEMALE(2);
	private int value;
	TypeEnum(int type) {
		this.type = type;
	}
}

比如:使用者在使用上面提供的类时,一般情况下使用方式就是:

People people1 = new People(TypeEnum.MAN);
People people2 = new People(TypeEnum.FEMALE);

类提供者虽然提供公有的枚举类,但是使用者生成实例时并不一定会按照你提供的枚举类来,这就有可能出现使用错误。
那么,如果使用静态工厂方法的话:

public class People {
	privte int type;
	
	private People(int type) {
		this.type = type;
	}
	
	public static getMan() {
		return new People(TypeEnum.MAN);
	}
	
	public static getFemale() {
		return new People(TypeEnum.Female);
	}
}

类的使用者看到这样的类时,能够迅速根据自己的需要选择对应的方法来生成实例,这种情况在类的参数多且复杂的情况下尤其能体现妙处。

3. 静态工厂方法的缺点

1)类如果不含公有的或者受保护的构造方法,将不能被子类化(被继承)
因为当前类向使用者提供自身实例是通过静态工厂方法且不希望通过其他方式获取自身实例时,构造方法必须是私有的,那么当前类就不能被继承,因此在设计代码架构时需要考虑当前类是否有被继承的使用场景,若有则不适合使用静态工厂方法。
当然,对于公有的静态工厂所返回的非公有类,同样是不能被继承的,如Collections中的任何类都是不能被继承的。
2) 静态工厂方法与普通的静态方法没有实质区别,容易混淆。
因此,需要制定一些标准或规范,比如对静态工厂方法使用一些惯用名称,如:valueOf,of,getInstance,newInstance,getType,newType

4. 总结

综上所述,静态工厂方法和公有构造函数各有千秋,静态工厂方法也不是万能的,得根据场景选择合适的方法,比如若类需要被继承则得使用公有构造函数来提供实例,比如单例模式下则使用静态工厂方法实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值