用静态工厂方法代替构造器
静态工厂方法和构造器是什么?
- 构造器即构造函数,用于生成类实例
- 静态工厂方法不同于设计模式中的工厂方法,而是一个返回类实例的静态方法
优点1——更利于理解
- 原文:如果构造器的参数本身没有确切地描述正在被返回的对象,那么具有适当名称的静态工厂会更容易使用,产生的代码也更易于阅读
如一个输入数字,返回其是奇数还是偶数的字符串,利用构造器的设计如下
class A {
private String value;
A(int num) {
if (num % 2 == 0) {
value = "even";
} else {
value = "odd";
}
}
public String getValue() {
return value;
}
}
而利用静态工厂方法的设计如下
class B {
public static String parityOf(int num) {
if (num % 2 == 0) {
return new String("even");
} else {
return new String("odd");
}
}
}
可看到类A中参数mum并不能指明待返回对象是什么,这会导致用途不明确,而类B的静态方法则更加明确的指出了其用途
优点2——可打破构造器签名的限制
- 原文:当一个类需要多个带有相同签名的构造器时,就用静态工厂方法代替构造器,并且仔细地选择名称以便突出静态工厂方法之间的区别
如下,对于具有int、int、String域的类C来说,不能同时有两个签名C_int_String(即代码中第二个构造器),然而这个限制可通过调整参数顺序打破(即代码中的第三个构造器),但这样会增加复杂性和不易理解
class C {
private int id;
private int age;
private String name;
C(int id, String name) {
}
/*C(int age, String name) {
}*/
C(String name, int age) {
}
}
而具有方法名的静态工厂方法则此不受限制
class D {
private int id;
private int age;
private String name;
public static D initIDAndName(int id, String name) {
D d = new D();
d.id = id;
d.name = name;
return d;
}
public static D initAgeAndName(int age, String name) {
D d = new D();
d.age = age;
d.name = name;
return d;
}
}
优点3——不必重复创建对象
- 原文:不可变类可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的重复对象
如上述判断奇偶数的程序,构造器的改进设计如下
final class E {
private static final String ODD = "odd";
private static final String EVEN = "even";
private String value;
E(int num) {
if (num % 2 == 0) {
value = EVEN;
} else {
value = ODD;
}
}
public String getValue() {
return value;
}
}
但仍然需要对重复创建value,而使用静态工厂方法则可以直接返回创建好的对象
final class F {
private static final String ODD = "odd";
private static final String EVEN = "even";
public static String parityOf(int num) {
if (num % 2 == 0) {
return EVEN;
} else {
return ODD;
}
}
}
优点4——可以限制实例存在
- 原文:静态工厂方法能够为重复的调用返回相同的对象,这样有助于类总能严格控制在某个时刻哪些实例应该存在
限制1——可以限制类是一个单例(每次返回都是同一对象)
class G {
G() {
}
}
class H {
private static H hInstance = new H();
private H() {
}
public static H getInstance() {
return hInstance;
}
}
限制2——限制类不可实例化(如上述F是工具类,可对其构造器私有化,避免创建F2导致重复的odd和even)
class F2 {
private static final String ODD = "odd";
private static final String EVEN = "even";
private F2() {
}
public static String parityOf(int num) {
if (num % 2 == 0) {
return EVEN;
} else {
return ODD;
}
}
}
同时限制2可以让存在final域的类不会存在两个相等的实例
优点5——可返回子类
如下构造器只能返回它本身的实例,而静态工厂方法可返回其子类
class J {
public static J create() {
return new K();
}
}
class K extends J {
}
进阶——可根据参数返回不同的子类型
class L {
public static L create(int num) {
switch (num) {
case 1:
return new M();
default:
return new N();
}
}
}
class M extends L {
}
class N extends L {
}
优点6——返回的类可延迟创建
- 原文:静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在
有点难以理解,主要是用于原文中提到的服务提供者框架,列如java提供一个统一的标准框架给供应商,如下(编写getService时还不知道返回的是什么服务,这就是延迟创建)
interface Service {
void beginService();
}
interface ServiceProvider {
Service getService();
}
class ServiceManager {
private static final Map<String, ServiceProvider> providers = new HashMap<>();
public static void registerProvider(String name, ServiceProvider provider) {
providers.put(name, provider);
}
public static Service getService(String name) {
ServiceProvider p = providers.get(name);
if (p == null) {
throw new IllegalArgumentException("No provider registered with name:" + name);
}
return p.getService();
}
}
供应商(这里举例为医院)提供一个服务(这里举例为养老服务),需要实现Service和ServiceProvider,并让ServiceProvider到ServiceManager去注册,如下
class oldService implements Service {
@Override
public void beginService() {
System.out.println("即将为你进行养老服务");
}
}
class XXXHospital implements ServiceProvider {
static {
ServiceManager.registerProvider("XXXHospital", new XXXHospital());
}
@Override
public Service getService() {
return new oldService();
}
}
而客户要使用供应商提供的服务,则需通过ServiceManager获取服务(需要先加载服务,并通过关键字XXXHospital找到对应的供应商),如下
try {
Class.forName("com.demo.demo0.XXXHospital");
Service xxxHospital = ServiceManager.getService("XXXHospital");
xxxHospital.beginService();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
这也正是java为数据库提供商和用户之间建立链接的方式,如下为连接jdbc代码
Class.forName("com.mysql.jdbc.Driver");
DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","123");
其他关于服务提供者框架的介绍可查看这篇文章https://www.iteye.com/blog/liwenshui322-1267202
缺点1——不含public或defualt构造器就不能被子类实例化
子类在调用构造器时需要调用父类的构造器,若父类不含public或defualt构造器就不能被子类实例化,但可通过子类复合父类的方式对父类进行构造
class O {
private O() {
}
public static O create() {
return new O();
}
}
class P {
private O o;
P() {
o = O.create();
}
}
缺点2——不容易发现
一般人都是利用构造器创建实例并调用方法,可能并不知道存在静态工厂方法,常用的静态工厂方法命名为
- from——类型转换
- of——参数合并
- instance或getInstance——单例
- create或newInstance——创建一个实例
- getType、newType或type——返回类型