我们说的23种设计模式其实在实际开发中常用的就十几种,从结构上讲分为三大类(创建型模式、行为型模式、结构型模式)。我们今天聊一下创建型设计模式中的 建造者模式,看看什么是建造者模式以及怎么使用和在什么情况下使用。当一个类的构造函数参数个数有很多个,而且这些参数有些是可选的参数,就可以考虑使用建造者模式了。
我们之前了解过工厂模式,工厂模式也是创建型模式这个大类中的一种。与建造者模式适用的场景也比较相似,如果说构建产品属性较多,建议用建造者模式。
什么是建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。(这么说都不明白是啥意思)我们用大白话来翻译一下:就是多个部件或零件,都可以装配到一个对象中,但是产生的结果却并不相同。我们用代码来实际举个例子:
老的写法
我们先定义试题类
@Getter
@Setter
@NoArgsConstructor
public class Question {
//题目名称
private String questionName;
//题型
private String questionType;
//知识点
private String questionKnowledge;
//视频题url地址
private String videoUrl;
//视频题视频时长
private String videoTime;
public Question(String questionName) {
this.questionName = questionName;
}
public Question(String questionName, String questionType) {
this.questionName = questionName;
this.questionType = questionType;
}
public Question(String questionName, String questionType, String questionKnowledge) {
this.questionName = questionName;
this.questionType = questionType;
this.questionKnowledge = questionKnowledge;
}
public Question(String questionName, String questionType, String questionKnowledge, String videoUrl) {
this.questionName = questionName;
this.questionType = questionType;
this.questionKnowledge = questionKnowledge;
this.videoUrl = videoUrl;
}
public Question(String questionName, String questionType, String questionKnowledge, String videoUrl, String videoTime) {
this.questionName = questionName;
this.questionType = questionType;
this.questionKnowledge = questionKnowledge;
this.videoUrl = videoUrl;
this.videoTime = videoTime;
}
}
题目有很多种,比如有视频题,游戏题,普通试题等。我们暂时先拿普通题和视频题举例。在这种情况下我们用大量if判断来创建不同的类型的题目,如下面的代码:
public class Test {
public static void main(String[] args) {
String name = "普通题目";
Question question = getQuestion(name);
System.out.println(JSONUtil.parse(question));
}
private static Question getQuestion(String name) {
Question question = new Question();
if("普通题目".equals(name)){
question.setQuestionName("填空题");
question.setQuestionType("1");
question.setQuestionKnowledge("2");
}
if("视频题目".equals(name)){
question.setQuestionName("预习视频");
question.setQuestionType("2");
question.setQuestionKnowledge("3");
question.setVideoUrl("http://www.baidu.com");
question.setVideoTime("23");
}
return question;
}
}
这里我们可以看到根据请求不同的题目类型,也可以完全创建出我们想要的题目,但是一个题目属性不可能只有这么一点属性,另外以后增加更多题型呢难道都要一个个if来扩展?如果那样以后接手这个代码的同事就得骂街了。
用建造者模式优化一波
- 创建我们的抽象建造者类,我们通过构建不同的抽象方法来创建不同的对象。
public abstract class QuestionBuilder {
/**
* 构建一个普通的试题
*/
public abstract void getNormalQestion();
/**
* 构建一个视频题
*/
public abstract void getVideoQestion();
/**
* 构建一个游戏题
*/
public abstract void getGameQestion();
/**
* @return 创建好对应的题目并返回
*/
public abstract Question buildQuestion();
}
- 创建具体建造者类。对抽象建造者类的抽象方法进行实现赋值,达到我们所需要创建不同属性的对象。
public class QuestionCreater extends QuestionBuilder {
protected Question question;
@Override
public void getNormalQestion() {
question = new Question();
question.setQuestionName("填空题");
question.setQuestionType("1");
question.setQuestionKnowledge("2");
}
@Override
public void getVideoQestion() {
question = new Question();
question.setQuestionName("预习视频");
question.setQuestionType("2");
question.setQuestionKnowledge("3");
question.setVideoUrl("http://www.baidu.com");
question.setVideoTime("23");
}
@Override
public void getGameQestion() {
//设置游戏属性
}
@Override
public Question buildQuestion() {
return question;
}
}
- 在创建一个指导类,用于指导QuestionCreater创建对应的对象。我理解其实这个类可以不用。
public class QuestionDirector {
private QuestionBuilder questionBuilder;
public QuestionDirector(QuestionBuilder questionBuilder) {
this.questionBuilder = questionBuilder;
}
//指导类指挥创建普通试题
public Question normalQ(){
questionBuilder.getNormalQestion();
return questionBuilder.buildQuestion();
}
//指导类指挥创建视频题
public Question videoQ(){
questionBuilder.getVideoQestion();
return questionBuilder.buildQuestion();
}
}
- 我们用客户端测试一下
QuestionBuilder questionBuilder = new QuestionCreater();
QuestionDirector questionDirector = new QuestionDirector(questionBuilder);
Question question = questionDirector.normalQ();
Question question2 = questionDirector.videoQ();
System.out.println(JSONUtil.parse(question));
System.out.println(JSONUtil.parse(question2));
//去掉指导类直接创建
QuestionBuilder questionBuilderWithOutDirector = new QuestionCreater();
questionBuilderWithOutDirector.getNormalQestion();
Question question3 = questionBuilderWithOutDirector.buildQuestion();
System.out.println(JSONUtil.parse(question3));
输出结果:
- {"questionKnowledge":"2","questionName":"填空题","questionType":"1"}
- {"videoUrl":"www.baidu.com","questionKnowledge":"3","questionName":"预习视频","videoTime":"23","questionType":"2"}
- {"questionKnowledge":"2","questionName":"填空题","questionType":"1"}
通过建造者模式的写法使的这个代码可读性高,以后扩展起来比较容易不需要对已有对象进行修复唉,不同类型的题目达到了解耦的目的。
我们再举个例子
还是上面的Question类,如果它有很多个构造方法,不同的构造方法创建成的对象是不同的,比如如果是适配类的题目则就需要给对应视频类的属性赋值。那我们就可以根据建造者模式进行逻辑改造。 我们创建一个抽象的题目构建器:
@NoArgsConstructor
public abstract class AbstractQuestionBuilder {
protected Question question;
public AbstractQuestionBuilder(Question question) {
this.question = question;
}
public AbstractQuestionBuilder setQuestionName(String questionName){
question.setQuestionName(questionName);
return this;
}
public AbstractQuestionBuilder setQuestionType(String questionType){
question.setQuestionType(questionType);
return this;
}
public AbstractQuestionBuilder setQuestionKnowledge(String questionKnowledge){
question.setQuestionKnowledge(questionKnowledge);
return this;
}
public AbstractQuestionBuilder setVideoUrl(String videoUrl){
question.setVideoUrl(videoUrl);
return this;
}
public AbstractQuestionBuilder setVideoTime(String videoTime){
question.setVideoTime(videoTime);
return this;
}
public Question build(){
return question;
}
}
再增加一个默认的创建类
public class DefaultQuestionBuilder extends AbstractQuestionBuilder {
}
这就可以了,那么接下来我们怎么使用呢?很简单
Question question = new DefaultQuestionBuilder()
.setQuestionName("填空题").setQuestionType("1").setQuestionKnowledge("2").build();
用以上2个例子相信大家就明白了什么是建造者模式了。
建造者模式的应用
- StringBuilder
我们最常用的StringBuilder类中就有建造者模式最直接的应用:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
@Override
public StringBuilder append(char c) {
super.append(c);
return this;
}
大家看看append方法是不是跟我们举的例子很像。
- beanDefinitionBuilder 还有就是我们常用的spring框架bean的定义关键类BeanDefinition的构建类
public AbstractBeanDefinition getBeanDefinition() {
this.beanDefinition.validate();
return this.beanDefinition;
}
public static BeanDefinitionBuilder genericBeanDefinition(String beanClassName) {
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
builder.beanDefinition.setBeanClassName(beanClassName);
return builder;
}
public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
builder.beanDefinition.setBeanClass(beanClass);
return builder;
}
建造者模式与工厂模式的区别
按照我的理解就是:建造者模式与工厂模式是基本一样,建造者模式仅仅只比工厂模式多了一个“指导类”的角色。在建造者模式中假如把这个类去掉,那么剩余的部分就可以看作是一个简单的工厂模式了。
建造者模式优点
- 首先,建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指导类中对整体而言可以取得比较好的稳定性。
- 其次,建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。
实际业务项目中对于建造者模式的应用
多个下拉菜单进行数据查询大家应该都见过如上图,界面是用于根据各个选项(名称、分校、学科、年级)来筛选搜索到的数据列表的。这个时候就很适合使用建造者模式来创建一个筛选类,在筛选类中定义各个筛选条件的成员变量,因为筛选条件的数量是不确定的,比如我只想根据名称搜索,我只想根据年级搜索,或者我既想根据名称又想根据分校来搜索,等等。。这个时候就可以通过给Builder设置不同的筛选条件来构造出一个符合预期的筛选对象,然后将筛选对象传过去即可。
作者注:欢迎关注笔者公号,定期分享IT互联网、金融、教育等工作经验心得、人生感悟,欢迎交流。