第六章 建造者模式 - 复杂对象的组装与创建
一、设计一个游戏角色
作为 RPG 游戏的一个重要组成部分,游戏角色拥有其特定的性别、脸型、服装、发型等外部特性。
无论何种造型的游戏角色,它们的创建步骤都大同小异,都需要逐步创建其组成部分,再将各组成部分装配成一个完整的游戏角色。
而建造者模式就是为了解决,各个组件组合为复杂对象的问题。
二、建造者模式
2.1 概述
建造者模式,将客户端与包含多个组成部分的复杂对象的创建过程分离,客户端无须知道复杂对象的内部组成部分与装配方式,只需要知道所需建造者的类型即可。
建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。属于一种对象创建型模式。
2.2 建造者模式中的几个角色
- Builder - 抽象建造者
- 为创建一个产品对象的各个部件指定抽象接口
- 一般声明两类方法:一类方法是buildPartX(),用于创建复杂对象的各个部件;另一类是getResult(),用于返回复杂对象
- 抽象类或接口
- ConcreteBuilder - 具体建造者
- 实现了Builder接口,实现各个部件的具体构造和装配方法
- Product - 产品角色
- 被构建的复杂对象,包含多个组成部件
- 由具体建造者创建该产品的内部表示并定义它的装配过程
- Director - 指挥者
- 又称为导演类,负责安排复杂对象的建造次序
- 客户端一般只需与指挥者交互,由客户端确定具体建造者的类型,并实例化具体建造者对象,然后通过指挥者类的构造函数或setter方法将对象传入指挥类中
2.3 复杂对象、建造者与指挥者
// 复杂对象,主要是包含多种不同类型的成员属性(部件)
class Product {
private String partA;
private Object partB;
private User partC;
}
abstract class Builder{
// 创建产品对象
protected Product product = new Product();
// buildPartX() 方法为产品对象的成员属性设值
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
// getResult 返回复杂对象
public Product getResult(){
return product;
}
}
class Director {
private Builder builder;
/**
* 通过构造方法或者setter方法注入一个抽象建造者,由调用者确定使用何种类型的建造者
*/
public Direct(Builder builder){
this.builder = builder;
}
/**
* construct 方法中调用builder对象的构造部件方法,返回一个产品对象
*/
public Product construct() {
build.buildPartA();
build.buildPartB();
build.buildPartC();
return builder.getResult();
}
}
需要扩展新的建造者时,只需要重新实现抽象建造者即可,无需修改源代码,系统扩展十分方便。
在客户端代码中,无需关系产品对象的具体组装过程,只需要指定具体建造者的类型即可。
2.4 建造者模式的使用
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
Product product = director.construct();
2.5 建造者模式与抽象工厂模式
建造者模式与抽象工厂模式很类似:
抽象工厂模式返回一系列相关的产品,而建造者模式返回一个完整的复杂产品。
抽象工厂模式中,客户端通过选择具体工厂来生成所需对象,而建造者模式中,客户端通过指定具体建造者类型并指导Director类去生成对象,侧重于一步步构建复杂对象,然后将结果返回。
类似于:抽象工厂生产出不同类型的汽车配件,而建造者模式就是汽车组装厂。
三、完整案例
import lombok.Getter;
import lombok.Setter;
// 角色类 - 复杂产品
@Getter
@Setter
public class Actor {
private String type; // 角色类型
private String sex; // 性别
private String face; // 脸型
private String costume; // 服装
private String hairStyle; // 发型
}
// 角色建造器 - 抽象建造者
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;
}
}
// 天使角色建造器 - 具体建造者
class AngelBuilder extends ActorBuilder {
@Override
public void buildType() {
actor.setType("天使");
}
@Override
public void buildSex() {
actor.setSex("女");
}
@Override
public void buildFace() {
actor.setFace("漂亮");
}
@Override
public void buildCostume() {
actor.setCostume("白裙子");
}
@Override
public void buildHairStyle() {
actor.setHairStyle("披肩长发");
}
}
// 恶魔角色建造器 - 具体建造者
class DevilBuilder extends ActorBuilder {
@Override
public void buildType() {
actor.setType("恶魔");
}
@Override
public void buildSex() {
actor.setSex("男");
}
@Override
public void buildFace() {
actor.setFace("丑陋");
}
@Override
public void buildCostume() {
actor.setCostume("丑陋");
}
@Override
public void buildHairStyle() {
actor.setHairStyle("光头");
}
}
// 游戏角色创建控制器 - 指挥者
class ActorController {
// 逐步构建复杂产品对象
public Actor construct(ActorBuilder buidler) {
buidler.buildType();
buidler.buildSex();
buidler.buildFace();
buidler.buildCostume();
buidler.buildHairStyle();
return buidler.createActor();
}
}
// 测试客户端,调用指挥者通过具体建造者来构建完整的复杂对象
class Client {
public static void main(String[] args) {
ActorBuilder actorBuilder = new AngelBuilder();
ActorController actorController = new ActorController();
Actor actor = actorController.construct(actorBuilder);
System.out.println(actor.getType() + ":" + actor.getFace());
}
}
四、扩展
4.1 省略Director指挥者
为了简化系统结构,可以将Director与抽象建造者Builder合并,如下:
// 角色建造器 - 抽象建造者
abstract class ActorBuilder {
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 buidler) {
buidler.buildType();
buidler.buildSex();
buidler.buildFace();
buidler.buildCostume();
buidler.buildHairStyle();
return actor;
}
}
4.2 钩子方法
建造者模式除了逐步构建一个复杂产品对象外,还可以通过Director类来精细控制产品的创建过程,如增加一个钩子方法来控制是否构建某个buildPartX()的调用。
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();
// 钩子方法 - 是否是光头,true则不进行buildHairStyle
public boolean isBareheaded(){
return false;
}
}
// 游戏角色创建控制器 - 指挥者
class ActorController {
// 逐步构建复杂产品对象
public Actor construct(ActorBuilder buidler) {
buidler.buildType();
buidler.buildSex();
buidler.buildFace();
buidler.buildCostume();
if(!isBareheaded){
buidler.buildHairStyle();
}
return buidler.createActor();
}
}
五、总结
优点
- 在建造者模式中,客户端不必知道产品内部组成细节,将产品本身和产品的创建过程解耦,使得相同创建过程能够创建出不同的产品对象
- 每个具体建造者都相对独立,可以方便地替换或增加具体建造者,符合“开闭原则”
- 将复杂产品的创建步骤分解在不同方法中,使创建过程更清晰,也更方便程序控制创建的过程
缺点
- 建造者模式所创建的产品具有许多共同点,其组成部分相似。如果产品间差异过大,则不适合建造者模式,使用范围有限
- 如果产品内部变化复杂,会导致需要许多具体建造者来实现该变化,会导致系统庞大,增加系统理解难度和运行成本
适用场景
- 需要生成的产品对象有复杂的内部结构,即多个成员属性
- 产品对象的属性相互依赖,需要指定其生成顺序
- 对象的创建过程独立于创建该对象的类。在建造者模式中,创建过程封装在指挥者类中,而不再建造者和客户端中
- 隔离复杂对象的创建和使用,且相同的创建过程能够创建不同的产品
六、练习
Sunny软件公司欲开发一个视频播放软件,为了给用户使用提供方便,该播放软件提供多种界面显示模式,如完整模式、精简模式、记忆模式、网络模式等。在不同的显示模式下主界面的组成元素有所差异,如在完整模式下将显示菜单、播放列表、主窗口、控制条等,在精简模式下只显示主窗口和控制条,而在记忆模式下将显示主窗口、控制条、收藏列表等。尝试使用建造者模式设计该软件。
/**
* 视频播放显示界面 - 具体产品
* 具体播放界面,包含显示菜单、播放列表、主窗口、控制条
* 可能包含的显示模式,完整模式、精简模式、网络模式
*/
@Setter
@Getter
public class TechWeb {
private String menu; // 菜单
private String list; // 播放列表
private String window; // 主窗口
private String control; // 控制条
}
/**
* 显示模式 - 抽象建造者
*/
abstract class ShowPattern {
protected TechWeb techWeb = new TechWeb();
public abstract void buildMenu();
public abstract void buildList();
public abstract void buildWindow();
public abstract void buildControl();
public TechWeb construct(){
buildControl();
buildList();
buildMenu();
buildWindow();
return techWeb;
}
}
/**
* 完整模式 - 具体建造者
*/
class FullPattern extends ShowPattern{
@Override
public void buildMenu() {
techWeb.setControl("完整控制条");
}
@Override
public void buildList() {
techWeb.setList("显示50条");
}
@Override
public void buildWindow() {
techWeb.setMenu("菜单列表50条");
}
@Override
public void buildControl() {
techWeb.setWindow("带所有菜单的窗口");
}
}
/**
* 精简模式
*/
class SimpPattern extends ShowPattern{
@Override
public void buildMenu() {
techWeb.setControl("完整的控制条");
}
@Override
public void buildList() {
techWeb.setList("不显示播放列表");
}
@Override
public void buildWindow() {
techWeb.setMenu("不显示菜单");
}
@Override
public void buildControl() {
techWeb.setWindow("不带菜单的窗口");
}
}
class Client {
public static void main(String[] args) {
ShowPattern simpPattern = new SimpPattern();
TechWeb web = simpPattern.construct();
System.out.println(web.getMenu());
}
}