1.引子
关于建立型设计模式,咱们已经分享了单例设计模式,和工厂设计模式。建立型设计模式其实不难理解,咱们抓住本质,所谓建立型设计模式都与对象的建立相关,而且在代码实现层面区分度都比较高,你应该都还记得如何实现单例设计模式(懒汉式、饿汉式),如何实现工厂模式(简单工厂、工厂方法、抽象工厂)对吧。java
那么今天,我将继续给你分享另一个在实际开发中,使用比较多的建立型设计模式,它就是建造者设计模式。而且我将重点给你分享为何须要建造者设计模式?以及建造者设计模式解决了什么问题?我想这个对于咱们掌握设计模式会更加剧要一些,知道作什么为何。至于代码层面,有时候还次要一些。程序员
让咱们开始吧。设计模式
2.案例
2.1.给一个须要建造者设计模式的理由
暂时咱们抛开设计模式,回归本质。请你轻轻的闭上眼睛,慢慢地深呼吸,是否是感受到一阵早春的气息扑面而来!测试
停!请你不要瞎想了。咱们是程序员,不是诗人。你应该听见的是键盘声、电流声声声入耳,绵绵不绝,而不是流水叮咚声!ui
回来吧,想想对象是如何建立的?this
平常开发中,当咱们须要一个对象的时候,咱们一般会经过构造方法建立对象,或者再进一步经过set方法初始化成员变量,像下面这样spa
用户实体类设计
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; } }
建立用户对象code
// 方式一:直接经过构造方法 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方法中?彷佛都不合适!咱们须要一个统一的地方,来统一处理校验逻辑。这个时候咱们本文的主角建造者设计模式,终于举手站了起来讲:是了,这些事情我来处理最合适了
经过平常开发中,咱们都很是熟悉的一个案例,以及思考1、思考2、思考三几个问题。咱们引出了建造者设计模式的适用场景,来简单总结一下
-
若是要建立的目标对象,成员变量比较多,且对象的使用比较灵活(每次建立对象,不须要所有的成员变量,而是部分红员变量的组合)
-
若是要建立的目标对象,成员变量须要进行必定的业务校验,甚至成员变量之间有业务依赖关系校验
基于以上两点,咱们适合使用建造者设计模式,也是我想分享给你:须要建造者设计模式的理由。接下来咱们经过代码示例来演示建造者设计模式的实现。
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