跳转至

建造者模式

分类:创建型模式。

类结构

classDiagram
    direction TB
    class Director {
        +construct()
    }
    class Builder {
        <<interface>>
        +buildPartA()
        +buildPartB()
    }
    class ConcreteBuilder {
        -Product product
        +buildPartA()
        +buildPartB()
        +getResult() Product
    }
    class Product
    Director --> Builder : 编排构建步骤
    Builder <|.. ConcreteBuilder
    ConcreteBuilder --> Product : 构建并持有

角色定义

角色 定义
Director(指挥者) 通过建造者接口安排构建步骤的顺序;需要复用固定构建流程时可使用这个角色。
Builder(建造者接口) 声明构建产品各部分的操作,使构建过程可以调用统一接口。
ConcreteBuilder(具体建造者) 实现各步骤,保存构建中的产品,并通过 getResult() 提供结果。不同实现可以构建不同表示。
Product(产品) 由多个构建步骤逐步组装出来的对象。

Head First 示例:逐步组装度假计划

本节对应《Head First 设计模式》附录中的建造者主题,具体类名与调用方式依据作者 2020 年第二版配套代码 builder/vacation 整理。这个实现用同一组建造操作安排两类度假计划:户外方案添加帐篷和徒步活动,城市方案添加酒店和演出活动,最后都得到 Vacation 对象。这里的类图是根据配套实现绘制的示意图。

VacationDirector 的实际职责是示例装配与调用:它只有 main(),在其中选择两种具体建造者,安排链式调用并打印结果。VacationBuilder 是抽象类,声明三个 addAccommodation() 重载和 addEvent(),由 getVacation() 创建结果;源码没有通用图中的 construct()、buildPartA() 或 getResult() 方法。建造者先累计住宿和活动数据,再把它们交给结果对象。

具体类名 对应角色 职责
VacationDirector 示例装配入口与指挥者 在 main() 中创建建造者、安排构建步骤、取得并打印度假计划。
VacationBuilder 抽象建造者 保存名称、住宿列表和活动列表,声明添加步骤并实现 getVacation()。
OutdoorsVacationBuilder 户外具体建造者 创建 Tent,将活动描述加工为徒步活动。
CityVacationBuilder 城市具体建造者 创建 Hotel,将活动描述加工为观看演出。
Vacation 构建结果 接收名称与两组列表,通过 toString() 展示度假计划。
Accommodation 住宿抽象基类 保存住宿名称与可选预订,声明 getLocation()。
Tent 户外住宿组成部分 保存营位号,通过 getLocation() 描述位置。
Hotel 城市住宿组成部分 保存房间号,通过 getLocation() 描述位置。
Reservation 预订数据组成部分 保存入住日期和住宿晚数。

示例 UML

图中保留 9 个类型,省略集合与日期库类型、部分访问器,以及无参和带日期等参数的 addAccommodation() 重载;具体子类重复实现的方法签名也作了省略。* 表示抽象方法,$ 表示静态方法,~ 表示包级可见。住宿列表会同时被建造者和结果引用,因此用普通关联表达持有关系。

classDiagram
    direction TB
    class VacationDirector {
        +main(String[] args) void$
    }
    class VacationBuilder {
        <<abstract>>
        ~String name
        +addAccommodation(String name) VacationBuilder*
        +addEvent(String event) VacationBuilder*
        +getVacation() Vacation
    }
    class OutdoorsVacationBuilder {
        +addAccommodation(String name) VacationBuilder
        +addEvent(String event) VacationBuilder
    }
    class CityVacationBuilder {
        +addAccommodation(String name) VacationBuilder
        +addEvent(String event) VacationBuilder
    }
    class Vacation {
        +setName(String name) void
        +setAccommodations(List~Accommodation~ accommodations) void
        +setEvents(List~String~ events) void
        +toString() String
    }
    class Accommodation {
        <<abstract>>
        ~String name
        +setReservation(Reservation r) void
        +getReservation() Reservation
        +getLocation() String*
    }
    class Tent {
        ~int siteNumber
        +setSiteNumber(int n) void
        +getLocation() String
    }
    class Hotel {
        ~int roomNumber
        +setRoomNumber(int n) void
        +getLocation() String
    }
    class Reservation {
        +setArrivalDate(int year, int month, int day) void
        +setNights(int nights) void
    }
    VacationDirector ..> VacationBuilder : 编排步骤并取得结果
    VacationDirector ..> OutdoorsVacationBuilder : 创建
    VacationDirector ..> CityVacationBuilder : 创建
    VacationBuilder <|-- OutdoorsVacationBuilder
    VacationBuilder <|-- CityVacationBuilder
    VacationBuilder ..> Vacation : 创建并设置数据
    VacationBuilder --> "0..*" Accommodation : accommodations
    Vacation --> "0..*" Accommodation : accommodations
    Accommodation <|-- Tent
    Accommodation <|-- Hotel
    Accommodation --> "0..1" Reservation : reservation
    OutdoorsVacationBuilder ..> Tent : 创建
    CityVacationBuilder ..> Hotel : 创建

协作过程

  1. VacationDirector.main() 先创建 OutdoorsVacationBuilder 并通过 VacationBuilder 引用调用它,随后另建 CityVacationBuilder 装配城市方案;两条调用链分别定义各自的住宿和活动安排。
  2. 带 name、year、month、day、nights、location 参数的 addAccommodation() 会创建 Reservation 并设置日期和晚数。户外实现将它交给 Tent,用 setSiteNumber() 设置营位;城市实现交给 Hotel,用 setRoomNumber() 设置房间。只传名称的重载则不创建预订。
  3. addEvent() 把活动转换成对应方案的文字并加入 events。添加住宿和活动的方法都返回当前建造者,所以示例能连续链式调用。
  4. getVacation() 每次新建 Vacation,调用 setName()、setAccommodations() 和 setEvents(),再把结果交回入口打印。此实现直接传递已有列表,既没有复制列表,也没有重置建造者;继续添加内容会影响此前结果中的共享列表。

固定版本源码:

评论