写在前面
继续更新,今天来学习建造者模式,进阶必备技能~~~
在软件工程领域,设计模式是一套通用、可复用的解决方案,用于解决在软件设计过程中产生的通用问题。它不是一个可以直接转成源码的设计,是一套开发人员在软件设计过程中应当遵循的规范。也就是说没有设计模式,软件依旧可以开发,只是后期维护可能变得不那么轻松。设计模式就是为了简化你的维护成本提升性能而设计的,不同的设计模式适用场景各异,具体的结合实际场景对待。
构造者模式
定义
本篇来学习关注对象创建类型中的构造者模式(Builder Pattern,也称建造者模式),构造者模式将一个复杂对象的创建与其表示分离,这样使得相同的构建过程可以创建不同的表示。
使用场景
接下来介绍构建者模式的使用场景,设计模式只是一种规范,因此了解在何种场景下选择哪种设计模式可能比具体实现显得更为重要。当一个类的构造函数中的参数个数多于4个,且存在必传和可选参数时,推荐使用构造者模式。
需求说明
假设现在有一个学生类,它有学号、姓名、性别、手机号、家庭住址和分数这6个属性,且学号和姓名是必传参数,其余4个是可选参数,此时我们该如何创建学生对象呢?通常我们有JavaBean和折叠构造函数这两种方式来创建学生对象。
JavaBean方式
最入门、最常用的则是JavaBean这一方式,只需定义一个Student类,并在其中通过getter和setter方法来给属性赋值:
代码语言:javascript复制public class Student {
//必填项
private int id;
private String name;
//可填项
private String sex;
private String mobile;
private String address;
private int score;
//getter和setter方法
@Override
public String toString() {
return "Student{"
"id=" id
", name='" name '''
", sex='" sex '''
", mobile='" mobile '''
", address='" address '''
", score=" score
'}';
}
}
之后在使用的时候在StudentTest类中定义如下代码:
代码语言:javascript复制public class StudentTest {
public static void main(String[] args) {
Student student = new Student();
student.setId(1);
student.setName("zhangsan");
student.setSex("男");
student.setAddress("北京");
student.setMobile("13100000000");
student.setScore(100);
}
}
这种方式的好处就是阅读方便,且只对有用的成员变量赋值,但是缺点也很明显:(1)成员变量不能被final关键字修饰,即无法使用常量。(2)对象的状态不连续,开发者必须连续调用6次属性set方法才能得到一个属性完整的对象,如果有N个属性,那么需要调用N次set方法,毫无疑问这种方式很容易出错。用户在不知道具体属性的情况下就获取对象,那么得到的则是一个不完整的对象。
折叠构造函数方式
针对JavaBean方式的状态不连续性弊端,开发者想出了折叠构造函数这一方式,该方式首先需要提供一个只包含必传参数的构造器,其次每增加一个可选参数就调用后续构造器并设置默认值,依次类推,最后需要提供一个包含所有参数的构造器:
代码语言:javascript复制public class Student {
//必填项
private int id;
private String name;
//可填项
private String sex;
private String mobile;
private String address;
private int score;
public Student(int id,String name){
this(id,name,"");
}
public Student(int id,String name,String sex){
this(id,name,sex,"");
}
public Student(int id,String name,String sex,String mobile){
this(id,name,sex,mobile,"");
}
public Student(int id,String name,String sex,String mobile,String address){
this(id,name,sex,mobile,address,0);
}
public Student(int id,String name,String sex,String mobile,String address,int score){
this.id =id;
this.name=name;
this.sex=sex;
this.mobile=mobile;
this.address=address;
this.score=score;
}
@Override
public String toString() {
return "Student{"
"id=" id
", name='" name '''
", sex='" sex '''
", mobile='" mobile '''
", address='" address '''
", score=" score
'}';
}
}
之后在使用的时候在StudentTest类中定义如下代码:
代码语言:javascript复制public class StudentTest {
public static void main(String[] args) {
Student student = new Student(1,"zhangsan");
}
}
可以发现这种方式最大的优点就是方便你创建实例,当你想快速创建对象时,就可以使用参数最少的构造器,实际上其它参数都是使用了自定义的默认值。缺点就是开发者必须了解每个构造器,否则在创建对象的时候无法调用正确的构造器,如果参数更多,那么这种方式所带来的就不是方便了,而是糟糕,因此这种方式适用于参数较少的场景。
传统的Builder模式
为了解决上述两种方式的弊端,Builder模式就诞生了,这里介绍传统的Builder模式以及对应的变种:
可以看到图中有5个角色:Product、Builder、ConcreteBuilder、Director和Client,下面分别进行介绍:
(1)Product,这是最终要生成的对象,如之前的学生对象Student。
(2)Builder,这是构建者的抽象基类(也可以是接口),里面定义了构建Product的抽象方法,请注意具体的构建者类需要实现这些抽象方法,并提供一个返回最终产品的方法Product getProduct();
。
(3)ConcreteBuilder,这是具体的构建者类,即Builder的实现类。
(4)Director,这是决定如何构建最终产品的算法,里面包括一个负责组装的方法void construct(Builder builder)
,在这个方法中我们可以调用builder方法来设置builder,最后通过builder的Product getProduct();
方法来获取最终的产品。
(5)Client,这是客户端,负责调用和使用生成的对象。
接下来我们通过Builder这一传统方式来构建Student对象,相应的步骤如下所示:
(1)创建最终需要生成的对象,即Student类,代码如下所示:
代码语言:javascript复制public class Student {
//必填项
private int id;
private String name;
//可填项
private String sex;
private String mobile;
private String address;
private int score;
public Student(int id,String name){
this.id=id;
this.name=name;
}
public void setSex(String sex) {
this.sex = sex;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public void setAddress(String address) {
this.address = address;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student{"
"id=" id
", name='" name '''
", sex='" sex '''
", mobile='" mobile '''
", address='" address '''
", score=" score
'}';
}
}
(2)创建构建者的抽象基类StudentBuilder,里面定义构建Product的抽象方法,且提供一个返回最终产品的方法Product getProduct();
,代码如下所示:
public abstract class StudentBuilder {
public abstract void setSex();
public abstract void setMobile();
public abstract void setAddress();
public abstract void setScore();
public abstract Student getStudent();
}
(3)创建具体的构建者类,假设现在我们要求构建小学生和中学生这两个对象,因此需生成这两个具体构建者类。其中用于构建小学生的PrimarySchoolStudentBuilder类代码如下:
代码语言:javascript复制public class PrimarySchoolStudentBuilder extends StudentBuilder {
private Student student;
public PrimarySchoolStudentBuilder(int id,String name){
student = new Student(id,name);
}
@Override
public void setSex() {
student.setSex("男");
}
@Override
public void setMobile() {
student.setMobile("13100000000");
}
@Override
public void setAddress() {
student.setAddress("北京");
}
@Override
public void setScore() {
student.setScore(100);
}
@Override
public Student getStudent() {
return student;
}
}
用于构建中学生的MiddleSchoolStudentBuilder类代码如下:
代码语言:javascript复制public class MiddleSchoolStudentBuilder extends StudentBuilder{
private Student student;
public MiddleSchoolStudentBuilder(int id,String name){
student = new Student(id,name);
}
@Override
public void setSex() {
student.setSex("女");
}
@Override
public void setMobile() {
student.setMobile("131111111111");
}
@Override
public void setAddress() {
student.setAddress("上海");
}
@Override
public void setScore() {
student.setScore(99);
}
@Override
public Student getStudent() {
return student;
}
}
(4)创建Director类,里面包括一个负责组装的方法void construct(Builder builder)
,StudentDirector类中的代码如下:
public class StudentDirector {
public void construct(StudentBuilder builder){
builder.setAddress();
builder.setMobile();
builder.setSex();
builder.setScore();
}
}
(5)创建客户端来调用和生成对应的对象,这里定义一个名为StudentTest的类,其中的代码如下所示:
代码语言:javascript复制public class StudentTest {
public static void main(String[] args) {
PrimarySchoolStudentBuilder psbuilder = new PrimarySchoolStudentBuilder(1,"zhangsan");
StudentDirector director = new StudentDirector();
director.construct(psbuilder);
Student primarySchoolStudent = psbuilder.getStudent();
System.out.println("Primary School Student is:" primarySchoolStudent.toString());
MiddleSchoolStudentBuilder msbuilder = new MiddleSchoolStudentBuilder(2,"lisi");
director.construct(msbuilder);
Student middleSchoolStudent = msbuilder.getStudent();
System.out.println("Middle School Student is:" middleSchoolStudent.toString());
}
}
最后运行这个main方法,可以看到控制台输出如下所示:
代码语言:javascript复制Primary School Student is:Student{id=1, name='zhangsan', sex='男', mobile='13100000000', address='北京', score=100}
Middle School Student is:Student{id=2, name='lisi', sex='女', mobile='131111111111', address='上海', score=99}
这种方式需要创建很多个类,要编写很多代码,因此在实际工作中用的并不是很多,而开发者在该Builder模式的基础上提出了变种Builder模式。
变种Builder模式
这里以学生类为例介绍变种Builder模式的操作步骤:(1)在要构建的类内部创建一个静态内部类Builder;(2)静态内部类中的属性与要构建的类中的属性完全一致;(3)构建类的构造函数的参数是静态内部类,且使用静态内部类的属性为构建类逐一赋值;(4)静态内部类提供属性的setter方法,且这些setter方法均返回Builder对象;(5)静态内部类提供一个build方法,里面new一个构建类对象,且参数是当前的Builder对象。
完整的代码如下所示:
代码语言:javascript复制public class Student {
//必填项
private final int id;
private final String name;
//可填项
private final String sex;
private final String mobile;
private final String address;
private final int score;
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getSex() {
return sex;
}
public String getMobile() {
return mobile;
}
public String getAddress() {
return address;
}
public int getScore() {
return score;
}
@Override
public String toString() {
return "Student{"
"id=" id
", name='" name '''
", sex='" sex '''
", mobile='" mobile '''
", address='" address '''
", score=" score
'}';
}
private Student(Builder builder){
this.id = builder.id;
this.name=builder.name;
this.sex = builder.sex;
this.mobile = builder.mobile;
this.address = builder.address;
this.score = builder.score;
}
public static class Builder {
//必填项
private int id;
private String name;
//可填项
private String sex;
private String mobile;
private String address;
private int score;
public Builder(int id,String name){
this.id = id;
this.name = name;
}
public Builder setSex(String sex){
this.sex = sex;
return this;
}
public Builder setMobile(String mobile){
this.mobile = mobile;
return this;
}
public Builder setAddress(String address){
this.address = address;
return this;
}
public Builder setScore(int score){
this.score = score;
return this;
}
public Student build(){
return new Student(this);
}
}
}
最后我们就可以在客户端StudentTest类中通过链式来调用,进而一步步的将对象构建出来:
代码语言:javascript复制public class StudentTest {
public static void main(String[] args) {
Student student = new Student.Builder(1,"zhangsan")
.setSex("男")
.setAddress("北京")
.setMobile("13100000000")
.setScore(100).build();
System.out.println(student);
}
}
可以看到变种Builder模式其实就4个步骤:(1)外部构造类的构造函数私有,且参数为静态内部类Builder;(2)静态内部类和外部构造类的属性完全一致;(3)在静态内部类中给每个属性提供setter方法,且返回的均为Builder对象;(4)静态内部类提供一个build方法,用于创建一个外部构造类对象。
通过上面的分析,可以很清晰的看到变种Builder模式省略了Director角色,同时将构建算法交给了Client端,接着在静态内部类中通过build方法生成了待构建的对象,最后采用了链式调用。
接下来对变种Builder模式进行总结:(1)相比于之前两种创建方式,它减少了对象构建过程中引入的多个构造函数、可选参数以及多个setter方法;(2)写法非常优雅,采用先赋值后创建的方式,通过调用build方法来创建构造类对象,保证了对象状态的完整性。
一般来说,我们在实际工作中用的多的则是变种Builder模式。