在我们生活中的对象比如汽车,它是由车轮、车架、发动机等组合起来的对象,但是往往使用的人只想要一辆汽车并不想知道创建汽车的过程此时就可以使用建造者模式。
在软件开发中,也存在大量类似汽车一样的复杂对象,它们拥有一系列成员属性,这些成员属性中有些是引用类型的成员对象。而且在这些复杂对象中,还可能存在一些限制条件,如某些属性没有赋值则复杂对象不能作为一个完整的产品使用;有些属性的赋值必须按照某个顺序,一个属性没有赋值之前,另一个属性可能无法赋值等。
复杂对象相当于一辆有待建造的汽车,而对象的属性相当于汽车的部件,建造产品的过程就相当于组合部件的过程。由于组合部件的过程很复杂,因此,这些部件的组合过程往往被“外部化”到一个称作建造者的对象里,建造者返还给客户端的是一个已经建造完毕的完整产品对象,而用户无须关心该对象所包含的属性以及它们的组装方式,这就是建造者模式的模式动机。
建造者模式定义
建造者模式是一个将复杂对象的构建过程与它的表示分离,使得同样的构建过程可以创建不同的表示,属于创建型模式,使用建造者模式对于用户而言只需要指定需要建造的类型就可以获取对象,建造过程及细节不需要了解。
可以看一下传统建造者模式UML类图:
其中主要的角色有四个:
- 产品(Product):要创建的产品类对象
- 建造者抽象(Builder):建造者的抽象类,规范产品对象的各个部分组成的建造,一般由子类实现具体的建造过程
- 建造者(ConcreteBuilder):具体的Builder类,根据不同的业务逻辑,具体化对象的各个组成部分的创建
- 调用者(Director):调用具体的建造者来创建对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完成创建或者按某种顺序创建
传统模式代码示例
举一个肯德基的套餐的例子。肯德基有很多套餐,肯德基的服务员需要根据用户的要求提供对应的的套餐。
@Data
@ToString
public class Combo {
private String title;
private String Hamburger;
private String chickenWings;
private String drumstick;
private String cola;
public Combo(String title){
this.title = title;
}
}
复制代码
public interface ComboBuilder {
Combo build();
void buildHamburger();
void buildChickenWings();
void buildDrumstick();
void buildCola();
}
复制代码
public class BComboBuilder implements ComboBuilder{
private final Combo mCombo = new Combo("B套餐");
@Override
public void buildHamburger() {
mCombo.setHamburger("两个套餐");
}
@Override
public void buildChickenWings() {
mCombo.setChickenWings("五个鸡翅");
}
@Override
public void buildDrumstick() {
mCombo.setDrumstick("三个鸡腿");
}
@Override
public void buildCola() {
mCombo.setCola("两个可乐");
}
public Combo build(){
return this.mCombo;
}
}
复制代码
public class AComboBuilder implements ComboBuilder {
private final Combo mCombo = new Combo("A套餐");
@Override
public void buildHamburger() {
mCombo.setHamburger("一个汉堡");
}
@Override
public void buildChickenWings() {
mCombo.setChickenWings("三个鸡翅");
}
@Override
public void buildDrumstick() {
mCombo.setDrumstick("三个鸡腿");
}
@Override
public void buildCola() {
mCombo.setCola("一个可乐");
}
public Combo build(){
return this.mCombo;
}
}
复制代码
public class Waiter {
private final ComboBuilder builder;
public Combo getCombo(){
return this.builder.build();
}
public Waiter(ComboBuilder builder){
this.builder = builder;
}
public void setBuilder() {
this.builder.buildChickenWings();
this.builder.buildCola();
this.builder.buildDrumstick();
this.builder.buildHamburger();
}
}
复制代码
public class Test {
public static void main(String[] args) {
AComboBuilder aComboBuilder = new AComboBuilder();
Waiter waiter = new Waiter(aComboBuilder);
waiter.setBuilder();
Combo combo = waiter.getCombo();
System.out.println(combo);
}
}
复制代码
传统模式优化-链式编程
public class AComboBuilder implements ComboBuilder {
private final Combo mCombo = new Combo("A套餐");
@Override
public ComboBuilder buildHamburger() {
mCombo.setHamburger("一个汉堡");
return this;
}
@Override
public ComboBuilder buildChickenWings() {
mCombo.setChickenWings("三个鸡翅");
return this;
}
@Override
public ComboBuilder buildDrumstick() {
mCombo.setDrumstick("三个鸡腿");
return this;
}
@Override
public ComboBuilder buildCola() {
mCombo.setCola("一个可乐");
return this;
}
public Combo build(){
return this.mCombo;
}
}
复制代码
public class Test {
public static void main(String[] args) {
AComboBuilder aComboBuilder = new AComboBuilder();
Combo combo = aComboBuilder.buildChickenWings()
.buildCola()
.buildDrumstick()
.buildHamburger()
.build();
System.out.println(combo);
}
}
复制代码
如上图所示不需要再一个一个写一个一个赋值了直接链式编程就好,还有一点上述的写法弃用了Director
,个人感觉没必要硬着头皮要套传统模式的写法,也是可以有一些变通,主要是要适合自己的项目,如果就是一个对象的创建很复杂我们也可以将builder
内嵌到类中如下:
@Data
@ToString
public class Combo {
private String title;
private String Hamburger;
private String chickenWings;
private String drumstick;
private String cola;
public Combo(String title){
this.title = title;
}
public static class Builder {
private final Combo mCombo = new Combo("A套餐");
public Builder buildHamburger() {
mCombo.setHamburger("一个汉堡");
return this;
}
public Builder buildChickenWings() {
mCombo.setChickenWings("三个鸡翅");
return this;
}
public Builder buildDrumstick() {
mCombo.setDrumstick("三个鸡腿");
return this;
}
public Builder buildCola() {
mCombo.setCola("一个可乐");
return this;
}
public Combo build(){
return this.mCombo;
}
}
}
复制代码
Combo combo = new Combo.Builder()
.buildChickenWings()
.buildCola()
.buildDrumstick()
.buildHamburger()
.build();
System.out.println(combo);
复制代码
根据自己的项目需求随机变通
建造者模式在源码中的使用
这里就举 StringBuilder
的例子吧
这种情况就很适用建造者模式应为在拼接字符串中为了提高性能需要做很多处理,如果放在外面每次使用都要调用那么多代码很不方便
建造者模式的应用场景
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
- 当你的对象的构造方法有三四个甚至更多的时候可以使用建造者模式替代
- 当初始化一个对象特别复杂,参数多,而且很多参数都有默认值的时候
建造者模式优缺点:
优点:
- 在建造者模式中,客户不必知道产品内部的组成细节
- 封装性好,创建和使用隔离
- 扩展性好,建造类直接独立,一定程度上解耦
缺点:
- 产生多余的建造类
- 产品内部发生变化,建造者都要修改,违反开闭原则同时修改成本也比较大
建造者模式和工厂模式的区别
- 建造者模式更加注重方法的调用顺序,工厂模式注重创建对象
- 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工程模式创建出来的都一样(这里可能有人疑问为啥一样,应为建造者模式对象的一些属性我们都是可以赋值的,可以有的有有的没有自然对象也就不一样了。工厂模式不一样只有一种创建方式要么属性全都有要么全没有所以一样)
- 关注维度不一样,工厂只关注对象的创建,建造者不仅要创建,还要知道这个对象是由那些部件组成的
- 建造者模式根据建造过程中的顺序不一样,最终的对象部件组成也有可能是不一样的(那些不关系调用顺序的类可就是一样的)