0x0、引言
今晚加班发版本,继续啃《设计模式之美》,本文是 设计模式与范式:创建型(46),建造者模式(Builder Design Pattern) 也有人叫做生成器模式,又是一个很常用的创建型设计模式,非常轻松的一节~
二手知识加工难免有所纰漏,感兴趣有时间的可自行查阅原文,谢谢。
0x1、为什么需要Builder模式
当类中的属性很多时,为了避免构造函数参数列表过长影响代码可读性与易用性,可以通过 构造函数配合set()方法 解决。
但如果存在下述情况中的一种,就要考虑使用Builder模式了:
- ① 强制创建对象的时候就设置一些必填属性(放构造方法中),如果必填属性很多,又会出现构造函数参数列表过长的问题。如果把必填属性放到set()中设置,那么校验必填属性是否已填写的逻辑就无处安放了;
- ② 类的属性间有一定的依赖关系或约束条件,如果用构造函数+set()方法的设计思路,依赖关系和约束条件的校验逻辑就无处安放了;
- ③ 希望创建不可变对象(创建后就不能修改内部属性),就不能在类中暴露set()方法;
0x2、Builder模式与Factory模式的区别
- 建造者模式 → 用于创建
一种类型
的复杂对象,通过设置不同的可选参数,定制化地创建不同的对象; - 工厂模式 → 用于创建
不同但是类型相关
的对象,由给定的参数来决定创建哪种类型的对象;
一个解释两者区别的经典例子
顾客进一家餐馆点餐,利用工厂模式,根据用户的不同选择,来制作不同的事物,如披萨、汉堡、沙拉。对于披萨来说,用户有各种配料可以定制,如芝士、西红柿、起司等,我们通过建造者模式根据用户选择的不同配料来制作披萨。
Builder模式一般由下述四个角色组成:
还是那句话,不要生搬硬套设计模式,四个角色不一定都要有,最常见的是Product中包裹一个Builder,直接生成,比如Android中的AlertDialog。
0x3、简单的代码示例
自定义游戏角色时,游戏角色由:性别,脸部,衣服三个部分组成,用户可以根据自己的喜好配置生成不同的角色。先写下没有引入Builder模式之前:
public class Role {
private static final int DEFAULT_SEX = 0;
private static final String DEFAULT_FACE = "大众";
private static final String DEFAULT_CLOTHE = "便装";
private String name; // 角色名,必填,故强制放到构造函数中设置
private int sex = DEFAULT_SEX; // 性别:0-男、1-女
private String face = DEFAULT_FACE;
private String clothe = DEFAULT_CLOTHE;
public Role(String name) { this.name = name; }
public int getSex() { return sex; }
public void setSex(int sex) { this.sex = sex; }
public String getFace() { return face; }
public void setFace(String face) { this.face = face; }
public String getClothe() { return clothe; }
public void setClothe(String clothe) { this.clothe = clothe; }
public String show() {
return "您创建了角色【" + name + "】 → " + (sex == 0 ? "男性角色 " : "女性角色 ") + face + "脸 身穿" + clothe;
}
}
// 测试用例
public class Game {
public static void main(String[] args) {
Role role = new Role("王司徒");
role.setFace("慈眉善目");
role.setClothe("古装");
System.out.println(role.show());
}
}
复制代码
运行结果如下:
接着用Builder模式改造一波:
// 产品类
public class Role {
private String name; // 角色名
private int sex; // 性别:0-男、1-女
private String face;
private String clothe;
public Role(Builder builder) {
this.name = builder.getName();
this.sex = builder.getSex();
this.face = builder.getFace();
this.clothe = builder.getClothe();
}
public String show() {
return "您创建了角色【" + name + "】 → " + (sex == 0 ? "男性角色 " : "女性角色 ") + face + "脸 身穿" + clothe;
}
}
// 建造者类
public class Builder {
private static final int DEFAULT_SEX = 0;
private static final String DEFAULT_FACE = "大众";
private static final String DEFAULT_CLOTHE = "便装";
private String name; // 角色名,必填
private int sex = DEFAULT_SEX; // 性别:0-男、1-女
private String face = DEFAULT_FACE;
private String clothe = DEFAULT_CLOTHE;
public Role build() {
// 将校验逻辑放到这里,必填项、约束条件等,只有都通过了才构建对象
if(name == null || name.isEmpty()) { return null; }
return new Role(this);
}
public String getName() { return name; }
public int getSex() { return sex; }
public String getFace() { return face; }
public String getClothe() { return clothe; }
// 也可以在set方法中进行逻辑校验
public Builder setName(String name) { this.name = name; return this; }
public Builder setSex(int sex) { this.sex = sex; return this; }
public Builder setFace(String face) { this.face = face; return this; }
public Builder setClothe(String clothe) { this.clothe = clothe; return this; }
}
// 测试用例
public class Game {
public static void main(String[] args) {
Role role = new Builder()
.setName("杰哥")
.setFace("超勇")
.build();
System.out.println(role.show());
}
}
复制代码
另外,使用创建者模式还可以避免对象存在 无效状态
,比如长方形,你通过set()设置了长度,没设置宽度,此时的长方形对象是没有意义的,或者说处于无效状态。而在创建者模式中,先设置好创建者的变量,然后再一次性地创建产品对象,就可以规避这个问题,使得对象一直处于 有效状态
。从示例代码也可以看出Builder的优缺点:
优点:
- 满足开闭原则 (建造者们都相对独立,替换/新增方便)
- 分离创建和使用 (调用方无需了解内部细节,通过统一方法接口调用即可组合成不同的对象实例)
- 可以自由的组合对象的创建过程 (将复杂的创建步骤拆解为单个独立的创建步骤,可自由拼接)
缺点:
- 代码量增加 (属性写两份,类Double)
- 使用范围有限 (对象存在较多共同点,如果对象实例间差异太大,就不适合使用Builder模式了)
- 容易引起超大类;
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END