《Java Design Patterns》第六章 建造者模式 - 复杂对象的组装与创建

第六章 建造者模式 - 复杂对象的组装与创建

一、设计一个游戏角色

作为 RPG 游戏的一个重要组成部分,游戏角色拥有其特定的性别、脸型、服装、发型等外部特性。

无论何种造型的游戏角色,它们的创建步骤都大同小异,都需要逐步创建其组成部分,再将各组成部分装配成一个完整的游戏角色。

而建造者模式就是为了解决,各个组件组合为复杂对象的问题。

建造者模式

二、建造者模式

2.1 概述

建造者模式,将客户端与包含多个组成部分的复杂对象的创建过程分离,客户端无须知道复杂对象的内部组成部分与装配方式,只需要知道所需建造者的类型即可。

建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。属于一种对象创建型模式。

2.2 建造者模式中的几个角色

  1. Builder - 抽象建造者
    1. 为创建一个产品对象的各个部件指定抽象接口
    2. 一般声明两类方法:一类方法是buildPartX(),用于创建复杂对象的各个部件;另一类是getResult(),用于返回复杂对象
    3. 抽象类或接口
  2. ConcreteBuilder - 具体建造者
    1. 实现了Builder接口,实现各个部件的具体构造和装配方法
  3. Product - 产品角色
    1. 被构建的复杂对象,包含多个组成部件
    2. 由具体建造者创建该产品的内部表示并定义它的装配过程
  4. Director - 指挥者
    1. 又称为导演类,负责安排复杂对象的建造次序
    2. 客户端一般只需与指挥者交互,由客户端确定具体建造者的类型,并实例化具体建造者对象,然后通过指挥者类的构造函数或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();
    }
}

五、总结

优点

  1. 在建造者模式中,客户端不必知道产品内部组成细节,将产品本身和产品的创建过程解耦,使得相同创建过程能够创建出不同的产品对象
  2. 每个具体建造者都相对独立,可以方便地替换或增加具体建造者,符合“开闭原则”
  3. 将复杂产品的创建步骤分解在不同方法中,使创建过程更清晰,也更方便程序控制创建的过程

缺点

  1. 建造者模式所创建的产品具有许多共同点,其组成部分相似。如果产品间差异过大,则不适合建造者模式,使用范围有限
  2. 如果产品内部变化复杂,会导致需要许多具体建造者来实现该变化,会导致系统庞大,增加系统理解难度和运行成本

适用场景

  1. 需要生成的产品对象有复杂的内部结构,即多个成员属性
  2. 产品对象的属性相互依赖,需要指定其生成顺序
  3. 对象的创建过程独立于创建该对象的类。在建造者模式中,创建过程封装在指挥者类中,而不再建造者和客户端中
  4. 隔离复杂对象的创建和使用,且相同的创建过程能够创建不同的产品

六、练习

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());
    }
}
文章作者: koral
文章链接: http://luokaiii.github.io/2019/07/05/读书笔记/《JavaDesignPatterns》/8.建造者模式/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自