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模式了)
- 容易引起超大类;