前言
网上关于建造者模式的文章已有很多,各式各样的定义、介绍、举例,相信不少人也都或多或少的了解过,此文仅记录博主的个人理解,可能与网上的一些建造者模式的样例不太一致。
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。
根据建造者模式的定义,建造者模式应该是用于当一个对象有较多可选参数(构建对象复杂),且我们不关心它的具体构建过程,只要它按照我们的指令完成了构建就行。
问题的产生
假如有一个Person
类,共有四个属性以及一个输出所有属性值的toString
方法,但是这四个属性中只有name
属性是必填的,且姓名、性别属性一旦确认就无法再被修改。
一、JavaBean模式
一般我们会使用JavaBean模式,写法如下:
public class Person {
private String name; // 姓名
private String gender; // 性别
private int age; // 年龄
private float weight; // 体重
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
@Override
public String toString() {
return "[name:" + name + ", gender:" + gender + ", age:" + age + ", weight:" + weight + "]";
}
}
那么,我们将这样来使用它。
Person person1 = new Person();
person1.setName("zhangsan");
person1.setAge(12);
person1.setGender("male");
person1.setWeight(50.6f);
String personInfo = person1.toString(); // 获取该person的信息
此种方式有一个默认的无参构造方法并且每个属性都有getter和setter方法。这种方法看起来很容易阅读和维护。
我们可以只创建一个空对象,然后只设置那些感兴趣的属性。但会导致对象构建过程的不连续、使用过程中的不稳定。
1. 该类的实例状态不固定。如果想创建一个Person
对象,该对象的4个属性都要赋值,那么直到所有的setXX方法都被调用之前,该对象都没有一个完整的状态(但是该对象已经创建了)。这意味着在该对象状态还不完整的时候,程序可能看见这个对象并且以为该对象已经构造完成。
Person person1 = new Person();
person1.setName("zhangsan");
person1.setAge(12);
String personInfo = person1.toString(); // 获取该person的信息
person1.setGender("male");
person1.setWeight(50.6f);
如上述代码,属性未设置完成之前,可能就调用了该对象的方法。从而使用了这个并未完全构造完成的对象的方法,得到的数据可能也是错误的。
2. 该Person
类是易变的。在对象创建完成之后,我们随时都可以通过setXX方法来改变属性的值(比如正常情况下姓名、性别是不会随便改变的)。
3. 因为构造函数是个无参构造函数,对象创建完成的时候,name
可能还未设置好值。
二、重叠构造模式
为了解决JavaBean模式的弊端,我们很容易想到解决方案。java类默认都有一个无参构造方法,那么我们自己来写必须传入某些参数的构造方法,但是为了控制好参数的选项,这里的四个参数将有8种构造方法(有一个为必填)。新代码如下:
public class Person {
private String name; // 姓名
private String gender; // 性别
private int age; // 年龄
private float weight; // 体重
public Person(String name) {
this.name = name;
}
public Person(String name, String gender) {
this.name = name;
this.gender = gender;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name, float weight) {
this.name = name;
this.weight = weight;
}
public Person(String name, int age, float weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
public Person(String name, String gender, float weight) {
this.name = name;
this.gender = gender;
this.weight = weight;
}
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public Person(String name, String gender, int age, float weight) {
this.name = name;
this.gender = gender;
this.age = age;
this.weight = weight;
}
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
@Override
public String toString() {
return "[name:" + name + ", gender:" + gender + ", age:" + age + ", weight:" + weight + "]";
}
}
在初始化对象时,构造函数内就把需要设置好的值传过去,这样就可以保证该对象一旦创建,就是一个完整的正确的对象,且这段代码保证了姓名、性别属性一旦确定无法更改,而年龄、体重属性可以再被更改。
但我们使用它的时候会觉得有点难用,它有太多构造方法,每次我们都要思考一下参数顺序、参数类型等:
Person person1 = new Person("Jake"); // 只有name属性
Person person2 = new Person("Jake", "female"); // 有name、gender属性
Person person3 = new Person("Jake", "female", 23); // 有name、gender、age属性
Person person4 = new Person("Jake", 23); // 有name、age属性
这种模式的缺陷是很致命的,可选属性越多,它的构造函数的数目就越大,而且当可选函数中同类型的属性较多时,更难以构造函数,在使用的时候我们也难以很清晰知道如何调用对应的构造函数且保证参数顺序正确。
问题的解决:建造者模式
建造者模式能很好的综合上述两种模式的优点而去除了它们的缺点,完美的实现我们的需求(安卓源码中常见的AlertDialog的构建就是使用的建造者模式)。
public class Person {
private String name; // 姓名
private String gender; // 性别
private int age; // 年龄
private float weight; // 体重
private Person(String name, String gender, int age, float weight) {
this.name = name;
this.gender = gender;
this.age = age;
this.weight = weight;
}
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
@Override
public String toString() {
return "[name:" + name + ", gender:" + gender + ", age:" + age + ", weight:" + weight + "]";
}
public static class Build {
private String name; // 姓名
private String gender; // 性别
private int age; // 年龄
private float weight; // 体重
public Build(String name) { // name属性必填
this.name = name;
}
public Build setGender(String gender) {
this.gender = gender;
return this;
}
public Build setAge(int age) {
this.age = age;
return this;
}
public Build setWeight(float weight) {
this.weight = weight;
return this;
}
public Person create() {
return new Person(name, gender, age, weight);
}
}
}
如代码所示,我们综合了上述两种模式,在Person类里面只有一个私有的传递所有参数的构造函数,且name、gender属性只能读,不能写。在Person类的内部增加了一个内部静态构造类,由这个类来规定了name属性必填,其他属性可通过setXXX来进行设置,当调用它的create方法时,才会来创建Person对象且返回。既保证了对象的完整型又不需要使用过多的构造函数,但创建对象之前需要先构造一个builder, 对性能会有些许影响。
一般我们在可选属性不少于4个的时候才考虑该模式。
总结
建造者模式的核心在于如何一步步构建一个包含多个组成部件的完整对象,在软件开发中,如果我们需要创建复杂对象并希望系统具备很好的灵活性和可扩展性可以考虑使用建造者模式。
主要优点
1. 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦。
2. 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
主要缺点
1. 需要定义具体建造者类来实现构造,对性能会有些许影响。
适用场景
1. 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
2. 需要生成的产品对象的属性相互依赖,需要指定其生成顺序(如上述例子假如规定必须按照name、gender、age、weighe的顺序来设置值,其他两种方式会难以控制,而建造者模式只有一个构造函数,属性顺序确定)。