建造者模式
1)概述
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
1.复杂对象
复杂对象是指包含多个成员属性的对象。
2.结构图
Builder(抽象建造者):它为创建一个产品Product对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是buildPartX(),它们用于创建复杂对象的各个部件;另一类方法是getResult(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。
ConcreteBuilder(具体建造者):它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。
Product(产品角色):它是被构建的复杂对象,包含多个组成部件。
Director(指挥者):它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。
客户端一般只需要与指挥者交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件或反射机制),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。
2)案例-基础方案
1.代码实现
Product-复杂对象
import lombok.Data;
@Data
public class Product {
private String partA;
private String partB;
private String partC;
}
Builder-抽象建造者
abstract class Builder {
protected Product product = new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回产品对象
public Product getResult() {
return product;
}
}
ConcreteBuilder-具体建造者
public class ConcreteBuilder extends Builder{
@Override
public void buildPartA() {
System.out.println("A---");
}
@Override
public void buildPartB() {
System.out.println("B---");
}
@Override
public void buildPartC() {
System.out.println("C---");
}
@Override
public Product getResult() {
product.setPartA("A");
product.setPartB("B");
product.setPartC("C");
return product;
}
}
Director-指挥者
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void setBuilder(Builder builder) {
this.builder = builder;
}
//产品构建与组装方法
public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
客户端
public class Client {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
Product product = director.construct();
System.out.println(product);
}
}
2.建造者模式与抽象工厂模式区别
- 建造者模式返回一个完整的复杂产品,而抽象工厂模式返回一系列相关的产品;
- 在抽象工厂模式中,客户端通过选择具体工厂来生成所需对象,而在建造者模式中,客户端通过指定具体建造者类型并指导Director类如何去生成对象,侧重于一步步构造一个复杂对象,然后将结果返回。
- 可以将抽象工厂模式看成一个汽车配件生产厂,生成不同类型的汽车配件,将建造者模式看成一个汽车组装厂,通过对配件进行组装返回一辆完整的汽车。
3)案例-完整解决方案
1.结构图
ActorController充当指挥者,ActorBuilder充当抽象建造者,HeroBuilder、AngelBuilder 和 DevilBuilder 充当具体建造者,Actor 充当复杂产品。
2.代码实现
复杂对象-Actor
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Actor {
private String type; //角色类型
private String sex; //性别
private String face; //脸型
private String costume; //服装
private String hairstyle; //发型
}
抽象建造者-ActorBuilder
abstract class ActorBuilder {
protected Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
public Actor createActor() {
return actor;
}
}
具体建造者
public class HeroBuilder extends ActorBuilder {
public void buildType() {
actor.setType("英雄");
}
public void buildSex() {
actor.setSex("男");
}
public void buildFace() {
actor.setFace("英俊");
}
public void buildCostume() {
actor.setCostume("盔甲");
}
public void buildHairstyle() {
actor.setHairstyle("飘逸");
}
}
----------------------------------------------------
public class AngelBuilder extends ActorBuilder {
public void buildType() {
actor.setType("天使");
}
public void buildSex() {
actor.setSex("女");
}
public void buildFace() {
actor.setFace("漂亮");
}
public void buildCostume() {
actor.setCostume("白裙");
}
public void buildHairstyle() {
actor.setHairstyle("披肩长发");
}
}
指挥者-ActorController
public class ActorController {
public Actor construct(ActorBuilder ab) {
Actor actor;
ab.buildType();
ab.buildSex();
ab.buildFace();
ab.buildCostume();
ab.buildHairstyle();
actor = ab.createActor();
return actor;
}
}
客户端
public class Client {
public static void main(String[] args) {
ActorController ac = new ActorController();
Actor actor;
actor = ac.construct(new AngelBuilder());
System.out.println(actor.getType() + "的外观:");
System.out.println("性别:" + actor.getSex());
System.out.println("面容:" + actor.getFace());
System.out.println("服装:" + actor.getCostume());
System.out.println("发型:" + actor.getHairstyle());
}
}
在建造者模式中,客户端只需实例化指挥者类,指挥者类针对抽象建造者编程,客户端根据需要传入具体的建造者类型,指挥者将指导具体建造者一步一步构造一个完整的产品,相同的构造过程可以创建完全不同的产品。
4)Director 深入
1.省略Director
在有些情况下,为简化系统结构,可以将Director和抽象建造者Builder进行合并,在Builder中提供逐步构建复杂产品对象的construct()方法。
由于Builder类通常为抽象类,因此可以将construct()方法定义为静态(static)方法,如果将游戏角色设计中的指挥者类ActorController省略,ActorBuilder类的代码修改如下:
/**
* 省略指挥者
*/
abstract class ActorBuilder02 {
protected static Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
public static Actor construct(ActorBuilder ab) {
ab.buildType();
ab.buildSex();
ab.buildFace();
ab.buildCostume();
ab.buildHairstyle();
return actor;
}
}
或者
/**
* 省略指挥者
*/
abstract class ActorBuilder03 {
protected Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
public Actor construct() {
this.buildType();
this.buildSex();
this.buildFace();
this.buildCostume();
this.buildHairstyle();
return actor;
}
}
以上两种对 Director 类的省略方式都不影响系统的灵活性和可扩展性,同时还简化了系统结构,但加重了抽象建造者类的职责,如果construct() 方法较为复杂,待构建产品的组成部分较多,建议还是将 construct() 方法单独封装在Director中,更符合"单一职责原则"。
2.引入钩子方法
建造者模式除了逐步构建一个复杂产品对象外,还可以通过Director类来更加精细地控制产品的创建过程,例如增加钩子方法(HookMethod) 来控制是否对某个 buildPartX() 的调用。
钩子方法的返回值类型通常为boolean类型,方法名一般为isXXX(),钩子方法定义在抽象建造者类中。
例如在游戏角色的抽象建造者类ActorBuilder中定义一个方法isBareheaded(),用于判断某个角色是否为"光头(Bareheaded)",在ActorBuilder中提供一个默认实现,其返回值为false,代码如下所示:
abstract class ActorBuilderWithHook {
protected Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
//钩子方法
public boolean isBareheaded() {
return false;
}
public Actor createActor() {
return actor;
}
}
如果某个角色无须构建头发部件,例如"恶魔(Devil)",则对应的具体建造器DevilBuilder将覆盖isBareheaded()方法,并将返回值改为true,代码如下所示:
public class DevilBuilder extends ActorBuilder {
public void buildType() {
actor.setType("恶魔");
}
public void buildSex() {
actor.setSex("妖");
}
public void buildFace() {
actor.setFace("丑陋");
}
public void buildCostume() {
actor.setCostume("黑衣");
}
public void buildHairstyle() {
actor.setHairstyle("光头");
}
//覆盖钩子方法
public boolean isBareheaded() {
return true;
}
}
此时,指挥者类 ActorController 的代码修改如下:
public class ActorControllerWithHook {
public Actor construct(ActorBuilderWithHook ab) {
Actor actor;
ab.buildType();
ab.buildSex();
ab.buildFace();
ab.buildCostume();
//通过钩子方法来控制产品的构建
if (!ab.isBareheaded()) {
ab.buildHairstyle();
}
actor = ab.createActor();
return actor;
}
}
当在客户端代码中指定具体建造者类型并通过指挥者来实现产品的逐步构建时,将调用钩子方法isBareheaded()来判断游戏角色是否有头发,如果isBareheaded()方法返回true,即没有头发,则跳过构建发型的方法buildHairstyle();否则将执行buildHairstyle()方法。
通过引入钩子方法,可以在Director中对复杂产品的构建进行精细的控制,不仅指定buildPartX()方法的执行顺序,还可以控制是否需要执行某个buildPartX()方法。
5)总结
建造者模式的核心在于如何一步步构建一个包含多个组成部件的完整对象,使用相同的构建过程构建不同的产品,在软件开发中,如果需要创建复杂对象并希望系统具备很好的灵活性和可扩展性可以考虑使用建造者模式。
1.优点
-
客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
-
每一个具体建造者都相对独立,可以方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象,由于指挥者类针对抽象建造者编程,增加新的具体建造者无须修改原有类库的代码,系统扩展方便,符合"开闭原则"。
-
可以精细控制产品的创建过程,将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰。
2.缺点
-
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,例如很多组成部分都不相同,不适合使用建造者模式,因此其使用范围受到一定的限制。
-
如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,增加系统的理解难度和运行成本。
3.适用场景
-
需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
-
需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
-
对象的创建过程独立于创建该对象的类,通过引入指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中。
-
隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。