常用的创建对象方式及存在问题
本文最后更新于:2022年6月29日 上午
使用构造方法
成员变量很多的时候,构造方法就没方便了.
举例:NutritionFacts是食品包装外面显示的营养成分标签,这里面有的营养成分是必须的:每一份的含量、每一罐的含量,其他的可选
1 |
|
从上面的代码可见,为了尽量满足用户需要,NutritionFacts提供了多个构造方法给用户使用,以后要是加字段就麻烦了
缓解上述问题的一种方法是使用JavaBeans模式,用无参构造方法,然后按照调用setXXX设置每个所需字段,示例如下所示
1 |
|
存在问题
- 首先,直观的看,这种做法违背了不可变对象的定义,创建出对象后,又用setXXX方法改变了成员变量
- 《Effective Java》的原话是在构造过程中JavaBean可能处于不一致的中的状态
使用静态工厂方法
相比静态工厂方法,构造方法存在以下五个典型问题
- 随着入参的不同,构造方法可以有多个,如下所示,然而都是同名的,这会给用户造成困惑,此刻用静态工厂方法,可以自由设置方法名(例如createWithName或者createWithAge),让用户更方便的选择合适的方法
- 使用构造方法意味着创建对象,而有时候我们只想使用,并不在乎对象本身是否是新建的
- 以动物类Animal.class为例,Animal类的构造方法创建的对象Animal的实例,而静态工厂方法的返回值声明虽然是Animal,但实际返回的实例可以是Animal的子类,例如Dog
- 静态工厂方法内部可以有灵活的逻辑来决定返回那种子类的实例
- 静态工厂方法还有一个优势:方法返回对象所属的类,在编写此静态方法时可以不存在,这句话有点晦涩,可以回想一下JDBC的获取connection的API,在编写此API的时候,并不需要知道MySQL的driver实现
存在问题
- 当您开发一个类时,如果决定对外提供静态工厂方法,那么将构造方法设为私有,就可以让用户只能选择静态工厂方法了,代码如下所示,然而,这样的Student类就无法被继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
private Student() {
}
public static Student newInstance(String name) {
Student student = new Student();
student.setName(name);
return student;
}
} - 一个类的代码中,可能已有一些静态方法,再加入静态工厂方法,一堆静态方法混杂在一起,用户从中找出静态工厂方法怕是不容易
使用builder
builder pattern,《Effective Java》中文版译作建造者模式,用builder对象来创建真正的对象实例,前面提到的构造方法和静态工厂的不足,在builder pattern这里都得到了改善
来看代码吧,以刚才的NutritionFacts为例,使用builder pattern后的代码如下,新增一个静态成员类Builder,可以设置Builder的每个成员变量,最后调用其build方法的时候,才真正创建NutritionFacts对象
1 |
|
以一个使用者的视角来看如何创建NutritionFacts对象,如下所示,流畅的写法,那些字段被设置以及具体的值都一目了然,最终build方法才会创建NutritionFacts对象,而且这是个不可变对象
1 |
|
存在问题
- 即便能解决构造方法和静态工厂自身的一些问题,builder pattern也不是万能的,缺点很明显:创建对象之前,先要创建builder对象,这在一些性能要求高、资源限制苛刻的场景中就不适合了
- 另外builder pattern适合的场景是成员变量多的时候,而这个所谓的多究竟如何理解呢?这可能是个小马过河的问题吧:见惯了几十个成员变量的类,再去看十几个成员变量的类,可能会有种很清爽的感觉,呃,扯远了,其实《Effective Java》的说法是四个或者更多个参数,就适合用builder apttern了
reference
常用的创建对象方式及存在问题
https://baymax55.github.io/2022/09/13/java/常用的创建对象方式及存在问题/