在设计类时,我们经常为其提供公有的构造器,通过构造器来实例化类。
但,在我学习设计模式时,有一点经常被提及,用静态工厂方法代替构造器。
今天,就来分析一下其中的利与弊。
首先,要先弄懂,构造函数的问题是什么?
一、构造函数的问题
当类有多个构造器时,静态工厂方法可以通过有意义的名称来区分不同的创建方式,使代码更易读。
有的杠精可能要说话了:通过改变参数列表中参数类型的顺序或个数,我就可以提供多个不同的构造器,有什么问题吗?
我想说的是,你这样并不符合编码规范,有如下几个问题:
1、方法名都是一个,容易混淆
编码规约规定,方法名称见名知意,你TMD都是一个名,搞笑呢?
如果某人看到new Person("John", 25)或new Person(25, "John"),他可能会混淆它们的作用,尤其是当参数数量和类型相同而顺序不同的时候。
public class Person {
private String name;
private int age;
private boolean isEmployed;
public Person(String name, int age) {
this.name = name;
this.age = age;
this.isEmployed = false; // 默认值
}
public Person(String name, boolean isEmployed) {
this.name = name;
this.age = 0; // 默认值
this.isEmployed = isEmployed;
}
public Person(int age, String name) {
this.name = name;
this.age = age;
this.isEmployed = false; // 默认值
}
}
参数相同,顺序不同,谁是谁,你记得住吗?
参数顺序不同的构造器可能导致调用时不小心传入了错误顺序的参数,结果得到一个非预期的对象。
如果顺序被搞错,编译器不会报错,但会导致运行时的逻辑错误。
Person person = new Person("John", 25); // 想调用第一个构造器
Person person2 = new Person(25, "John"); // 想调用第三个构造器,结果参数位置错误,产生错误的数据
2、扩展性问题
当你需要扩展类并添加更多构造器时,如果通过改变参数顺序和数量来实现重载,可能会迅速导致构造器的组合方式爆炸,难以维护,估计连你自己都看不明白了。
这谁写的代码?拉出去砍了。
现在裁员盛行,你要小心了。
3、构造函数每次被调用都要创建一个新对象
这能有什么问题?这不都是Java中默认的嘛,每当你使用 new 关键字调用一个类的构造函数时,都会创建一个对象实例。
创建新对象涉及分配内存和初始化对象,这在性能上有一定开销。如果在短时间内需要频繁创建和销毁大量对象,这种开销可能会显著影响系统性能。
构造函数的每次调用都创建新对象,也会使对象的缓存和复用变得困难。
在数据库连接池中,我们希望复用已经存在的连接,而不是每次都创建一个新连接,这时构造函数的行为就显得不合适。
二、静态工厂方法如何解决构造器的问题呢?
问题1:方法名都是一个,容易混淆
静态工厂方法可以通过有意义的命名来避免这种混淆,见名知意。
这是静态工厂方法的一个重要优势。不同于构造函数必须与类名相同,静态工厂方法可以根据其创建对象的逻辑和用途使用不同的名称,从而提高代码的可读性和明确性。
通过静态工厂方法,如createChild、createEmployedAdult和createUnemployedAdult,你可以清晰地表达每个方法的用途,避免了通过构造器参数类型来区分的混淆问题。每个方法的名字直接说明了创建对象的用途,提升了代码的可读性。
改进后的代码:
public class Person {
private String name;
private int age;
private boolean isEmployed;
// 私有构造器,防止直接实例化
private Person(String name, int age, boolean isEmployed) {
this.name = name;
this.age = age;
this.isEmployed = isEmployed;
}
// 静态工厂方法,解决方法名混淆问题
public static Person createChild(String name) {
return new Person(name, 0, false); // 默认年龄为0,未就业
}
public static Person createEmployedAdult(String name, int age) {
return new Person(name, age, true); // 成人,已就业
}
public static Person createUnemployedAdult(String name, int age) {
return new Person(name, age, false); // 成人,未就业
}
}
问题2:扩展性问题
静态工厂方法可以通过添加新的方法来扩展对象创建的方式,从而提高代码的扩展性。
假如需要增加新的构造方式(如裁员人员),你可以轻松地通过添加createRetiredPerson静态工厂方法来实现,而无需更改现有的构造函数或担心重载导致的代码复杂化。静态工厂方法的扩展性使得代码更容易维护和增强。
// 新增静态工厂方法,支持扩展需求
public static Person createLayoffPerson(String name) {
return new Person(name, 35, true); // 默认35岁,在职
}
问题3:构造函数每次被调用都要创建一个新对象
静态工厂方法可以通过缓存对象、实现单例模式或其他优化策略,避免每次都创建新对象,从而提升性能和资源利用效率。
public class Person {
private String name;
private int age;
private boolean isEmployed;
private static final Person DEFAULT_CHILD_INSTANCE = new Person("Default Child", 0, false);
// 私有构造器
private Person(String name, int age, boolean isEmployed) {
this.name = name;
this.age = age;
this.isEmployed = isEmployed;
}
// 使用缓存的实例,避免每次都创建新对象
public static Person getDefaultChildInstance() {
return DEFAULT_CHILD_INSTANCE;
}
// 其他静态工厂方法
...
}
对于重复多次的调用,静态工厂方法可以返回同一个对象,这就可以控制存在哪些实例,被称为实例受控。