Builder Pattern 建造者模式,是创建型模式的一种。其核心内涵是将一个复杂对象的构建过程与表示分离,使得同样的构建过程可以创建出该对象的不同表示
客户订车
在现实世界中,一个物品通常是由若干个子部分组成,例如电脑、汽车、手机等。对于一个物品的每个子部分来说,虽然有多种选择,但是各个子部分的组合通常又是固定的。以汽车为例,其包含轮胎、发动机、底盘等零部件。虽然每个零部件有很多型号可供选择。但现在有一个车厂,它只生产三款车型,每个车型零部件的组合如下所示。可以看到,对于任一一款车型而言,其各个零部件型号的组合是固定的。当客户向该车厂订购一辆车时,客户只需告知车厂订购的车型即可,而无需关心这辆车在制造时该用哪种型号的零部件
车型 |
轮胎 |
底盘 |
发动机 |
car1 |
tire1 |
chassis2 |
engine1 |
car2 |
tire2 |
chassis1 |
engine2 |
car3 |
tire1 |
chassis3 |
engine3 |
现在回到我们的更加熟悉的虚拟世界——软件开发,我们在构造一个对象时,经常显式地传入各种成员变量的参数来构造出一个我们所需的对象。但如果我们每次构建对象时,发现其成员变量参数的组合情况是一定的话,是不是就可以考虑借鉴一下我们上面所提到的客户订车的那个例子?经验告诉我们在软件开发中,大量重复的代码说明是有优化提升空间的
模式思想
现在我们好好想一想,我们能从客户订车这个小故事中能学到什么呢?首先对于客户来说,不论他订购哪种型号的车,他只需告诉车厂所订购的车型就好了;其次对于车厂而言,他不论制造什么型号的车,生产线的流程基本是固定的,比如这里可以是先装底盘、再装发动机、最后装轮胎。即构建过程是一致的,唯一区别的就是在制造不同车型的生产流水线上,所使用的零部件型号不同。这样就可以实现同样的生产线流程可以制造出不同型号的汽车
至此我们就从中提取到了一种思想,在软件开发领域我们称之为Builder Pattern建造者模式。在建造者模式中有4个角色:
- Product : 产品,即最终被建造出的对象。就像上文所言的那样车厂最终是要生产出产品(即汽车)给客户的。同样地在软件开发中,作为一种创建型的设计模式,它的最终目的也是为了创建出一个对象
- Director : 导演/指挥者。这个角色的目的是指挥建造者完成一个固定的建造流程。就像车厂里的生产流水线一样,无论哪种型号的车其所需经过工序都是一致的,流水线负责推动整个建造作业的流程顺序
- Builder、ConcreteBuilder : 抽象建造者、具体建造者。车厂里三种车型都需要工人们来装底盘、装发动机、装轮胎,这些统一的行为就可以通过抽象建造者来约定,具体到软件开发中就是通过接口或抽象类来实现;而同一个行为在不同的生产线上会因为使用不同型号的零部件而产生差异,对应到软件开发中就是会有若干个具体建造者来实现成员变量参数的差异组合
小试牛刀
光说不练假把式,现在我们试着通过Builder Pattern建造者模式来实际演示下,以便让大家更好的理解这种设计模式,这里我们以手机为例进行说明,这里Phone类的角色就是Product
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
@Data public class Phone {
private String brand;
private String os;
private Integer ramSize;
private Double price; }
|
我们可以看到Phone这个类包含了4个成员变量:品牌、操作系统、内存容量、售价,所以对于Builder抽象建造者来说,其目标就是定义设置各个成员变量的行为,以及建造完成后获取产品的行为 getPhone()。这里我们是通过接口来进行定义的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
public interface PhoneBuilder {
void setBrand();
void setOs();
void setRamSize();
void setPrice();
Phone getPhone(); }
|
现在就需要通过具体的建造者ConcreteBuilder来实现不同类型的手机建造,这里我们提供了3个具体建造者,用于建造3种不同类型的手机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
|
public class IPhoneBuilder implements PhoneBuilder{
private Phone phone;
public IPhoneBuilder() { phone = new Phone(); }
@Override public void setBrand() { phone.setBrand("Apple"); }
@Override public void setOs() { phone.setOs("IOS"); }
@Override public void setRamSize() { phone.setRamSize(2); }
@Override public void setPrice() { phone.setPrice(6666.66); }
@Override public Phone getPhone() { return phone; } } ...
public class MiPhoneBuilder implements PhoneBuilder {
private Phone phone;
public MiPhoneBuilder() { phone = new Phone(); }
@Override public void setBrand() { phone.setBrand("Xiao Mi"); }
@Override public void setOs() { phone.setOs("Android"); }
@Override public void setRamSize() { phone.setRamSize(8); }
@Override public void setPrice() { phone.setPrice(1999.99); }
@Override public Phone getPhone() { return phone; } } ...
public class NokiaPhoneBuilder implements PhoneBuilder {
private Phone phone;
public NokiaPhoneBuilder() { phone = new Phone(); }
@Override public void setBrand() { phone.setBrand("Nokia"); }
@Override public void setOs() { phone.setOs("Symbian"); }
@Override public void setRamSize() { phone.setRamSize(1); }
@Override public void setPrice() { phone.setPrice(100.00); }
@Override public Phone getPhone() { return phone; } }
|
至此,我们就差一个流水线——Director,来推动、指挥具体的建造者依次完成各个建造工序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public class PhoneDirector {
public void construct(PhoneBuilder phoneBuilder) { phoneBuilder.setBrand(); phoneBuilder.setOs(); phoneBuilder.setRamSize(); phoneBuilder.setPrice(); } }
|
现在,我们就完成了一个基于Builder Pattern的手机建造了。现在让我们一起来看看怎么订购手机吧,我们首先需要构造一个导演实例——phoneDirector,然后构造一个期望订购的手机的建造者。当我们把这个建造者交给导演时,就”自动”完成了手机的建造,我们这些客户再也不需要Care构造手机过程中成员变量的参数设置了。最后,当我们从建造者手中取出已经建造好的手机就可以了。至此,我们就完成了一个手机对象实例的构造过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
public class BuilderPatternDemo {
public static void demo1() { PhoneDirector phoneDirector = new PhoneDirector();
IPhoneBuilder iPhoneBuilder = new IPhoneBuilder(); phoneDirector.construct(iPhoneBuilder); Phone iPhone = iPhoneBuilder.getPhone(); System.out.println(iPhone);
MiPhoneBuilder miPhoneBuilder = new MiPhoneBuilder(); phoneDirector.construct(miPhoneBuilder); Phone miPhone = miPhoneBuilder.getPhone(); System.out.println(miPhone);
NokiaPhoneBuilder nokiaPhoneBuilder = new NokiaPhoneBuilder(); phoneDirector.construct(nokiaPhoneBuilder); Phone nokiaPhone = nokiaPhoneBuilder.getPhone(); System.out.println(nokiaPhone); } }
|
测试结果如下:
简化版 Builder Pattern
上文介绍的建造者模式,更多的是适用于子部分组合结果一定的场景。而对于子部分组合情况多样的场景就无法使用了。这样在构造对象实例时,要么通过构造器一次性进行初始化,要么不断地调用setXxxx方法进行初始化。当其成员变量较多时,就会显得代码十分繁琐冗长、不够优雅。为了解决这一问题,简化版 Builder Pattern 应运而生。这里我们同样以Phone为例通过使用简化版 Builder Pattern 重写该类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
|
@ToString public class Phone {
private String brand;
private String os;
private Integer ramSize;
private Double price;
public static Phone.PhoneBuilder builder() { return new Phone.PhoneBuilder(); }
public Phone(String brand, String os, Integer ramSize, Double price) { this.brand = brand; this.os = os; this.ramSize = ramSize; this.price = price; }
public static class PhoneBuilder {
private String brand;
private String os;
private Integer ramSize;
private Double price;
public PhoneBuilder() { }
public Phone.PhoneBuilder brand(String brand) { this.brand = brand; return this; }
public Phone.PhoneBuilder os(String os) { this.os = os; return this; }
public Phone.PhoneBuilder ramSize(Integer ramSize) { this.ramSize = ramSize; return this; }
public Phone.PhoneBuilder price(Double price) { this.price = price; return this; }
public Phone build() { return new Phone(brand, os, ramSize, price); } } }
|
可以看到简化版 Builder Pattern 最明显的一个特点就是其不像传统 Builder Pattern 那样有多个类来扮演各个分工明显的角色。其只有一个Product类,然后通过内部类的方式实现了一个具体建造者。而Director导演这个角色也不需要了,其是通过”客户”按需的显式地指定建造流程。下面即是一个简化版的使用示例,相比较于构造器、setXxxx来进行初始化的姿势而言,链式调用的方式会显得更加简洁优雅
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public class BuilderPatterDemo {
public static void demo2() {
Phone P40Pro = Phone.builder() .brand("华为") .os("鸿蒙") .ramSize(12) .price(9999.99) .build();
System.out.println(P40Pro); } }
|
测试结果如下
Note
- Java开发者可以通过Lombok的@Builder注解自动生成该类简化版Builder Pattern的相关代码
参考文献
- Head First 设计模式 弗里曼著