这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战
1. 万能的乐高
乐高大家应该都玩过吧,一些小小的零件却可以拼成各种各样的东西,基本上只有你想不到,没有它做不到的。 乐高的积木套装就是将不同规格的零件组合在一起,最终可以拼成一个成品如城堡、汽车等。
我们试着用代码来描述这个过程,首先定义零件的抽象类和四个具体零件类:
// 零件抽象基类
public abstract class Part {
protected String specs;//规格
// 省略构造函数
public abstract String description();
}
public class Brick extends Part {
@Override
public String description() {
return specs + "砖块";
}
}
Plate、Tile、Gear类均继承Part类,重写description
方法即可,这里省略代码。
定义乐高套件,它由四种零件组成:
@Getter
public class Lego {
private List<Brick> bricks = new ArrayList<>();
// 其他零件列表...
Lego(){}
// 展示套件
public void show() {
// 依次输出所有组件
}
private void print(List<? extends Part> parts) {
if (!parts.isEmpty()) {
System.out.println("(" + parts.get(0).description() + ")" + " x " + parts.size());
}
}
}
乐高套件有了,零件也有了,怎么将零件进行组装呢? 让客户端自行创建零件,并设置到套件里吗?根据迪米特法则,客户端只关心自己拿到的是一个乐高套件,它并不关心这个套件是怎么组装起来的啊!
因此,我们定义一个构建器接口,它可以构建四种零件,并最终生成一个套件。除此之外,再编写两个构建器实现,分别是:简易套装、困难套装。
// 乐高套装构建器
public interface Builder {
// 构建砖块
Builder buildBrick();
// 构建其他零件...
// 构建乐高
Lego build();
}
// 建议套件
public class SimpleBuilder implements Builder{
private Lego lego = new Lego();
@Override
public Builder buildBrick() {
for (int i = 0; i < 10; i++) {
lego.getBricks().add(new Brick("2x2"));
}
return this;
}
// 构建其他零件...
@Override
public Lego build() {
return this.lego;
}
}
// 困难套件
public class ComplexBuilder implements Builder{
private Lego lego = new Lego();
@Override
public Builder buildBrick() {
for (int i = 0; i < 100; i++) {
lego.getBricks().add(new Brick("2x2"));
}
return this;
}
// 构建其他零件...
@Override
public Lego build() {
return this.lego;
}
}
只要有构建器,就可以生成一个乐高套件,且不同的构建器可以构建不同的套件。
为了避免高层模块深入到构建者内部的实现类,因此创建一个导演类,由导演类来复杂调用构建器生成套件实例。
public class Director {
public Lego createSimple(){
return new SimpleBuilder()
.buildBrick()
.buildPlate()
.buildTile()
.buildGear()
.build();
}
public Lego createComplex(){
// 通过ComplexBuilder创建...
}
}
这就是建造器模式!
2. 建造者模式的定义
建造者模式通用类图将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- Product:产品类。
- Builder:抽象建造器,定义产品的组装步骤。
- ConcreteBuilder:具体的建造器,完成产品的组装实现。
- Director:各个子组件的构建顺序,并最终告诉Builder开始建造完整对象。
建造者模式的定义看着还是挺绕的,什么是「将一个复杂对象的构建与它的表示分离」呢? 抽象建造器Builder负责定义要构建一个复杂对象,需要先构建N个子组件,至于具体的构建器是如何构建这些子组件的,上层是不关心的,这就是:将一个复杂对象的构建与它的表示分离。
什么是「使得同样的构建过程可以创建不同的表示」? 还是和上面一样,抽象建造器Builder只负责定义构建一个复杂对象前需要先构建N个子组件,这个过程是固定不变的,即为“同样的构建过程”。具体的构建器构建子组件的细节可以是不同的,另外导演类Director可以按照不同的顺序来构建子组件,也可能会对最终的成品产生不同的影响,即为“不同的表示”。
3. 建造者模式的优点
- 良好的封装性,建造者模式使得客户端无需知道产品的构建过程和组成部分,符合迪米特法则。
- 建造者容易扩展,乐高的例子中,如果要实现一个变态难的套件是非常简单的。
- 建造者可以对建造过程逐步细化,而不会对其他模块产生影响。
4. 建造者模式的使用场景
- 产品类非常复杂,需要很多的参数,同时这些参数有很多是可选的,则非常适合用建造者模式。
- 相同的方法,不同的执行顺序会产生不同的结果(导演类)。
- 构建一个产品步骤十分繁琐,可以通过建造者模式来对外屏蔽构建细节。
5. 总结
使用建造者模式需要生成额外的建造者对象,需要JVM开辟额外的内存空间,GC的压力也会增大,这是它的缺点,因此建造者模式不宜滥用,如果是构建非常简单的对象,就不要使用该模式了。 相反,如果一个产品类十分复杂,客户端构建起来会非常吃力,则应该首先考虑使用建造者模式来构建对象。