1.引子
关于创建型设计模式,我们已经分享了单例设计模式,和工厂设计模式。创建型设计模式其实不难理解,我们抓住本质,所谓创建型设计模式都与对象的创建相关,并且在代码实现层面区分度都比较高,你应该都还记得如何实现单例设计模式(懒汉式、饿汉式),如何实现工厂模式(简单工厂、工厂方法、抽象工厂)对吧。
那么今天,我将继续给你分享另外一个在实际开发中,使用比较多的创建型设计模式,它就是建造者设计模式。并且我将重点给你分享为什么需要建造者设计模式?以及建造者设计模式解决了什么问题?我想这个对于我们掌握设计模式会更加重要一些,知道做什么为什么。至于代码层面,有时候还次要一些。
让我们开始吧。
2.案例
2.1.给一个需要建造者设计模式的理由
暂时我们抛开设计模式,回归本质。请你轻轻的闭上眼睛,慢慢地深呼吸,是不是感觉到一阵早春的气息扑面而来!
停!请你不要瞎想了。我们是程序员,不是诗人。你应该听见的是键盘声、电流声声声入耳,绵绵不绝,而不是流水叮咚声!
回来吧,想一想对象是如何创建的?
日常开发中,当我们需要一个对象的时候,我们通常会通过构造方法创建对象,或者再进一步通过set方法初始化成员变量,像下面这样
用户实体类
public class User{
private String name;
private Integer sex;
private Integer age;
public User(){}
public User(String name, Integer sex, Integer age){
this.name = name;
this.sex = sex;
this.age = age;
}
/***************省略get方法****************/
public void setName(String name){
this.name = name;
}
public void setSex(Integer sex){
this.sex = sex;
}
public setAge(Integer age){
this.age = age;
}
}
创建用户对象
// 方式一:直接通过构造方法
User user = new User("小明",1,18);
// 方式二:通过无参构造方法 + set方法
User user = new User();
user.setName("小明");
user.setSex(1);
user.setAge(18);
以上创建用户User对象的代码,我们非常熟悉。那么你是否曾想过,这样创建使用对象的方式有什么问题吗?你可以先简单的思考一下,再往下看我提出的问题。
-
思考一:如果我们要创建的对象,成员变量非常多。比如说有20个、30个、甚至更多。你会怎么做?通过带参数构造方法,构造方法会因为参数太多,从而导致代码易用性、可读性差。试想调用一个带30个参数的构造方法,心理阴影面积有多大,像这样
User user = new User(参数1,参数2,参数3,......参数30);
- 思考二:你说User类的成员变量虽然多,但是我们在实际使用的时候,并不是所有成员变量都要赋值。这样一来就好办了,通过无参数构造方法+set方法,像这样
User user = new User();
user.set参数1(参数1);
user.set参数2(参数2);
......给需要的成员变量,通过set方法赋值......
......用不到的成员变量,就不用管了,问题解决......
- 思考三:通过无参数构造方法+set方法,从某种角度上解决了我们的问题。但是,如果成员变量需要有校验逻辑(比如非空,比如取值范围等);再比如成员变量之间有依赖关系校验逻辑(性别男,退休年龄60岁;性别女,退休年龄55岁)。这样的校验逻辑,该放到什么地方处理呢?构造方法中?set方法中?似乎都不合适!我们需要一个统一的地方,来统一处理校验逻辑。这个时候我们本文的主角建造者设计模式,终于举手站了起来说:是了,这些事情我来处理最合适了
通过日常开发中,我们都非常熟悉的一个案例,以及思考一、思考二、思考三几个问题。我们引出了建造者设计模式的适用场景,来简单总结一下
-
如果要创建的目标对象,成员变量比较多,且对象的使用比较灵活(每次创建对象,不需要全部的成员变量,而是部分成员变量的组合)
-
如果要创建的目标对象,成员变量需要进行一定的业务校验,甚至成员变量之间有业务依赖关系校验
基于以上两点,我们适合使用建造者设计模式,也是我想分享给你:需要建造者设计模式的理由。接下来我们通过代码示例来演示建造者设计模式的实现。
2.2.代码示例
2.2.1.用户实体User类
/**
* 用户实体类
*
* @author ThinkPad
* @version 1.0
* @date 2021/3/5 20:11
*/
public class User {
/**
* 姓名
* */
private String name;
/**
* 性别:1 男 ; 2 女
* */
private Integer sex;
/**
* 退休年龄:男 60 岁;女 55岁
* */
private Integer retirementAge;
/*****************get方法***************************/
public String getName() {
return name;
}
public Integer getSex() {
return sex;
}
public Integer getRetirementAge() {
return retirementAge;
}
/*****************set方法***************************/
public void setName(String name) {
this.name = name;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public void setRetirementAge(Integer retirementAge) {
this.retirementAge = retirementAge;
}
/**
* 用户建造者设计类
*/
public static class UserBuilder{
/**拷贝用户实体类成员变量*/
private String name;
private Integer sex;
private Integer retirementAge;
/**无参数构造方法*/
UserBuilder(){}
/**相当于set方法,注意返回builder对象*/
public User.UserBuilder name(String name){
this.name = name;
return this;
}
public User.UserBuilder sex(Integer sex){
this.sex = sex;
return this;
}
public User.UserBuilder retirementAge(Integer retirementAge){
this.retirementAge = retirementAge;
return this;
}
/**
* 建造者设计模式代码实现,核心方法
* 此方法中,校验姓名不能为空
* 此方法中,模拟校验性别与退休年龄的依赖关系,即:
* 1.性别男,退休年龄60岁
* 2.性别女,退休年龄55岁
* */
public User build(){
// 校验姓名不能为空
if(this.name == null || "".equals(this.name)){
throw new RuntimeException("用户姓名不能为空!");
}
// 校验性别与退休年龄依赖关系
if(this.sex == 1 && this.retirementAge < 60){
throw new RuntimeException("男人命苦,退休年龄必须是60岁以后!");
}else if(this.sex == 2 && this.retirementAge < 55){
throw new RuntimeException("女人也不容易,退休年龄必须是55岁以后!");
}
// 创建用户对象
User user = new User();
user.setName(this.name);
user.setSex(this.sex);
user.setRetirementAge(this.retirementAge);
return user;
}
}
}
2.2.2.用户建造者UserBuilder类
建造者设计模式的应用,很多时候我们会直接把建造者类,在它所对应的目标类中实现,为的是增强代码的内聚性。比如UserBuilder,在User类的内部。
这里我单独把UserBuilder类的代码贴出来,是方便你更清晰的看到建造者设计模式实现的关键地方,主要有三个
-
建造者类的成员变量,与目标类的成员变量一致
-
建造者类,需要给每个成员变量提供对应的,相当于set赋值方法
-
建造者类,重点是提供build方法,该方法实现业务逻辑的统一处理,以及构建目标类对象并返回
/**
* 用户建造者设计类
*/
public static class UserBuilder{
/**拷贝用户实体类成员变量*/
private String name;
private Integer sex;
private Integer retirementAge;
/**无参数构造方法*/
UserBuilder(){}
/**相当于set方法,注意返回builder对象*/
public User.UserBuilder name(String name){
this.name = name;
return this;
}
public User.UserBuilder sex(Integer sex){
this.sex = sex;
return this;
}
public User.UserBuilder retirementAge(Integer retirementAge){
this.retirementAge = retirementAge;
return this;
}
/**
* 建造者设计模式代码实现,核心方法
* 此方法中,校验姓名不能为空
* 此方法中,模拟校验性别与退休年龄的依赖关系,即:
* 1.性别男,退休年龄60岁
* 2.性别女,退休年龄55岁
* */
public User build(){
// 校验姓名不能为空
if(this.name == null || "".equals(this.name)){
throw new RuntimeException("用户姓名不能为空!");
}
// 校验性别与退休年龄依赖关系
if(this.sex == 1 && this.retirementAge < 60){
throw new RuntimeException("男人命苦,退休年龄必须是60岁以后!");
}else if(this.sex == 2 && this.retirementAge < 55){
throw new RuntimeException("女人也不容易,退休年龄必须是55岁以后!");
}
// 创建用户对象
User user = new User();
user.setName(this.name);
user.setSex(this.sex);
user.setRetirementAge(this.retirementAge);
return user;
}
}
2.2.3.使用建造者设计模式
2.2.3.1.正常执行案例
public static void main(String[] args) {
// 创建建造者对象
User.UserBuilder builder = new User.UserBuilder();
// 通过建造者,创建用户对象
User user = builder.name("小明")
.sex(1)
.retirementAge(60)
.build();
// 输出用户对象
System.out.println("用户名称:" + user.getName()
+ ",性别:" + user.getSex()
+ ",退休年龄:" + user.getRetirementAge());
}
#执行结果==================================
用户名称:小明,性别:1,退休年龄:60
Process finished with exit code 0
2.2.3.2.测试校验异常案例
public static void main(String[] args) {
// 创建建造者对象
User.UserBuilder builder = new User.UserBuilder();
// 通过建造者,创建用户对象
User user = builder.name("小明")
.sex(1)
.retirementAge(59)// 设置退休年龄 59岁,不满足男人 60岁退休的约束
.build();
// 输出用户对象
System.out.println("用户名称:" + user.getName()
+ ",性别:" + user.getSex()
+ ",退休年龄:" + user.getRetirementAge());
}
#执行结果==================================
Exception in thread "main" java.lang.RuntimeException: 男人命苦,退休年龄必须是60岁以后!
at com.anan.edu.common.design.pattern.create.builder.User$UserBuilder.build(User.java:94)
at com.anan.edu.common.design.pattern.create.builder.BuilderDemo.main(BuilderDemo.java:19)
Process finished with exit code 1