1.2.1 设计模式:Builder——建造者模式_representationbuilder_梦想拒绝零风险的博客-程序员信息网

技术标签: Builder 模式  译文  建造者模式  设计模式  

Intent(意图)

将一个复杂对象的构建(construction)与其表示(representation)分离,这样同样的构建过程可以创建出不同的表示。

Motivation(动机)

一个RTF(Rich Text Format)文档转换格式的阅读器必须可以将RTF转换成多种文本格式。该阅读器必须能将RTF文档转换成无格式的ASCII文本,或者转换为可交互编辑的文本插件(text widget)。但问题是,转换的可能性种类是开放的。因此必须能在不修改阅读器的前提下添加新的转换。

解决方法之一是使用一个TextConverter对象配置RTFReader类,该对象可以将RTF转换为另一种文本表示。当RTFReader传入RTF文档时,它使用TextConverter执行转换。一旦RTFReader识别出一个RTF token(RTF标记,纯文本或者RTF控制字(RTF control word)),它向TextConverter发布一个转换该token的请求。TextConverter对象负责执行数据转换和以特定格式表示这个token.

TextConverter的子类根据不同的转换和格式区分开来。例如ASCIIConverter忽略纯文本以外的任何请求。而TeXConverter接受所有的请求从而在文本中生成包含所有风格的TeX表示。TextWidgetConverter将构建一个复杂的user interface(用户界面)对象,使得用户可以阅读和编辑文本。

译者注:上文说的是一种多格式文本,类似HTML文件,根据不同的标签,将文本表示成不同形式。这里也是根据不同标记使用不同的转换器转换文本格式。

image

每种转换器类都为创建和装配一个复杂对象提供机制,并将其隐藏在一个抽象接口中。转换器与阅读器隔离,阅读器负责解析(parsing)RTF文档。

Buider模式满足(captures)所有这些关系。在该模式中,每一个converter(转换器)类称为一个Builder,而这个阅读器称为Director(指挥者)。应用到这个例子中,Builder模式将翻译文本格式(也就是RTF文档的解析)这个逻辑从怎样创建和表示转换后格式中分离出来。这使得我们可以复用RTFReader的解析逻辑从RTF文档创建出不同的文本表示——只需使用不同的TextConverter子类配置RTFReader

Applicability(应用)

以下情况使用Builder模式:

  • 创建复杂对象的逻辑应该独立于该对象的组成部分以及他们的组成方式(how they’re assembled)。
  • 构造过程必须允许被构造对象有不同的表示

Structure(结构)

image

Participants(参与者)

  • Builder(TextConverter)
    • 指定一个创建Product对象的部件(part)的抽象接口
  • ConcreteBuilder(ASCIIConverter,TeXConverter,TextWidgetConverter)
    • 通过实现Builder接口构造(constructs)和组装(assembles)产品部件
    • 定义并保存(keep track of)它创建的表示。
    • 提供获取(retrieving)产品的接口(例如GetASCIIText,GetTextWidget)
  • Director(RTFReader)
    • 使用Builder接口构建对象。
  • Product(ASCIIText, TeXText, TextWidget)
    • 表示正在构造的复杂对象。ConcreteBuilder建造了产品(product)的内部表示,通过ConcreteBuilder定义的方法组装成了它(译者注:指product)。
    • 含有定义组成部分(constituent parts)的类,含有将部件(parts)组装成最终结果的接口。

Collaborations(协作)

  • 客户端创建Director对象并使用想要(desired)的Builder对象配置它(译者注:它指Director)。
  • 每当需要建造product的一个部件时Director就通知Builder
  • Builder处理来自Director的请求并向product中添加part
  • 客户端从Builder中获取产品。

下面的交互图阐述了BuilderDirector如何与客户端协作。
image

Consequences(结果)

这里是Builder模式的结果:

  1. 它使得你可以改变产品的内部表示。Builder对象为director提供一个构建product的抽象接口。该接口允许builder隐藏产品的表示及其内部结构。它同时也隐藏了产品的组装方式。因为产品是通过一个抽象接口构建,你要更改产品的内部表示只需定义一个新的builder.
  2. 它隔离了构建(construction)代码和表示(representation)代码。Buider模式通过封装复杂对象的构建方式和表示方式提升了模块性。客户端无需知道有关定义product内部结构的类的任何信息;这样的类不会出现在Builder的接口中。

    每个ConcreteBuilder包含了创建和组装特定种类的product的所有代码。代码只编写一次,之后不同的Director可以复用它从同样的部件组中(the same set of parts)建造Product变体。在先前的RTF例中,我们可以为RTF之外的格式定义一个阅读器,叫SGMLReader,然后使用同样的TextConverter去产生SGML文档的ASCIITextTeXTextTextWidget译文。

  3. 它让你对构建过程有更精细的控制。不像一步到位地构造product的创建模式,Builder模式在director的控制下,一步一步地构建product.只有当product完成了,director才会从builder中获取它。因此相比其他创建型模式,Builder接口更能反映product的构建过程。这使得你对构建过程有更精细的控制,并由此控制最终的(resulting)product的内部结构。

Implementation(实现)

通常,有一个抽象的Builder类为每一个组件定义一个操作,director会用这个操作请求创建该组件。该操作默认情况下不做任何事。ConcreteBuilder类根据它要创建的组件覆盖该操作。

这里是其他需要考虑的实现问题:
1. 组装和构造接口。Builder通过一步一步的方式构建他们的product.因此Builder类接口必须足够全面以让所有的concrete builder都可以构造product

一个关键设计问题涉及到构建和组装过程的模型。将构造请求结果简单附加到`product`这种模型通常是足够的。在RTF实例中,`builder`转换下一个`token`并将其附加到它目前已转换的文本中。

但是有时候你可能需要访问早期构造的`product`的部分。在实例代码的`Maze`(迷宫)示例中,`MazeBuilder`接口让你在已存在的房子之间添加一扇门。像自底向上构建的解析树这样的树结构也是如此。在那种情形中,`builder`要返回孩子节点给`director`,随后`director`将它们回传给`builder`从而建立父节点。
  1. 为何product没有抽象类?在通常情形下,经concrete builder产生的product在表示上有很大差别以至于给不同的product定义一个公有的父类没有任何好处。在RTF实例中,ASCIITextTextWidget对象不可能有也不需要一个公有的接口。因为客户端通常会使用合适的concrete builder配置director,客户端只需要知道Builder哪一个concrete子类在用,并对其product进行相应处理.
  2. Builder中的方法默认为空实现。在C++中,build方法有意不声明为纯虚拟成员函数,而是定义为空方法,让客户端只覆盖他们感兴趣的操作。

Sample Code(示例代码)

我们将定义多种以MazeBuilder类的一个builder(对象)为参数的CreateMaze成员函数。
MazeBuilder类为建造maze定义了如下接口:

class MazeBuilder {
    public:
        virtual void BuildMaze() { }
        virtual void BuildRoom(int room) { }
        virtual void BuildDoor(int roomFrom, int roomTo) { }
        virtual Maze* GetMaze() { return 0; }
    protected:
        MazeBuilder();
};

该接口可以创建三个东西:(1)maze, (2)特别房号的room, (3)编了房号的房子之间的门。GetMaze操作返回mazeclient. MazeBuilder的子类将覆盖该操作返回他们建造的maze.

MazeBuilder所有的maze-building操作默认不做任何事。他们没有声明为纯虚拟,以使得派生类只覆盖他们感兴趣的方法。

给定了MazeBuilder接口,我们可以修改CreateMaze成员函数以将该builder作为参数。

Maze* MazeGame::CreateMaze (MazeBuilder& builder) {
    builder.BuildMaze();

    builder.BuildRoom(1);
    builder.BuildRoom(2);
    builder.BuildDoor(1, 2);
    return builder.GetMaze();
}

将该版本的CreateMaze与原版对比。注意builder是如何隐藏Maze的——即定义room,doorwall的这些类,以及这些部分是如何组装完成最终的maze。某些人可能会想到这里有表示roomdoor的类,但是并没有看到表示wall的类(but
there is no hint of one for walls.)。这是为了让更改maze的表示方式更加容易,这样所有MazeBuilderclient都不需要更改。

与其他创建型模式一样,Builder模式封装了对象的创建方式(how objects get created),本例中是通过MazeBuilder定义的接口。这意味着我们可以复用MazeBuilder建造不同种的mazes.CreateComplexMaze操作给出了一个示例:

Maze* MazeGame::CreateComplexMaze (MazeBuilder& builder) {
    builder.BuildRoom(1);
    // ...
    builder.BuildRoom(1001);

    return builder.GetMaze();
}

注意MazeBuilder本身并不会创建maze;它的主要目的只是定义一个创建maze的接口。为了方便,它主要定义了空实现。MazeBuilder的子类做实际的工作。

子类StandardMazeBuilder是一个建造简单maze的实现。它将正在创建的maze存放在变量_currentMaze中。

class StandardMazeBuilder : public MazeBuilder {
public:
    StandardMazeBuilder();

    virtual void BuildMaze();
    virtual void BuildRoom(int);
    virtual void BuildDoor(int, int);

    virtual Maze* GetMaze();
private:
    Direction CommonWall(Room*, Room*);
    Maze* _currentMaze;
};

ConmonWall是决定两个room之间的公共wall的方向的一个工具操作。
StandardMazeBuilder构造器简单地初始化_currentMaze.

StandardMazeBuilder::StandardMazeBuilder () {
    _currentMaze = 0;
}

BuildMaze实例化一个Maze的同时其他操作会组装并最终返回给client(使用GetMaze)。

void StandardMazeBuilder::BuildMaze () {
    _currentMaze = new Maze;
}
Maze* StandardMazeBuilder::GetMaze () {
    return _currentMaze;
}

BuildRoom操作创建一个room并建造其周围的wall:

void StandardMazeBuilder::BuildRoom (int n) {
    if (!_currentMaze->RoomNo(n)) {
        Room* room = new Room(n);
        _currentMaze->AddRoom(room);

        room->SetSide(North, new Wall);
        room->SetSide(South, new Wall);
        room->SetSide(East, new Wall);
        room->SetSide(West, new Wall);
    }
}

要在两个房间之间创建一扇门,StandardMazeBuilder查看maze中的这两间房并找到它们的相邻的墙:

void StandardMazeBuilder::BuildDoor (int n1, int n2) {
    Room* r1 = _currentMaze->RoomNo(n1);
    Room* r2 = _currentMaze->RoomNo(n2);
    Door* d = new Door(r1, r2);

    r1->SetSide(CommonWall(r1,r2), d);
    r2->SetSide(CommonWall(r2,r1), d);

现在客户端可以结合StandardMazeBuilder使用CreateMaze创建一个maze:

Maze* maze;
MazeGame game;
StandardMazeBuilder builder;

game.CreateMaze(builder);
maze = builder.GetMaze();

我们可以将所有StandardMazeBuilder操作放到Maze中并让每一个Maze建造自身。但是Maze的轻量化有助于更容易理解和修改Maze,并且让StandardMazeBuilder容易从Maze中分离。最重要地,将两者分离可以让你有多种MazeBuilder,每一种使用不同的roomwalldoor类。

CountingMazeBuilder是一个更加特殊(exotic)的MazeBuilder.该builder根本不会创建maze;他只是记录将要创建的不同种类的组件的数目。

class CountingMazeBuilder : public MazeBuilder {
    public:
        CountingMazeBuilder();

        virtual void BuildMaze();
        virtual void BuildRoom(int);
        virtual void BuildDoor(int, int);
        virtual void AddWall(int, Direction);

        void GetCounts(int&, int&) const;
    private:
        int _doors;
        int _rooms;
};

构造器初始化counters,并覆盖MazeBuilder操作相应增加数量。

CountingMazeBuilder::CountingMazeBuilder () {
    _rooms = _doors = 0;
}

void CountingMazeBuilder::BuildRoom (int) {
    _rooms++;
}

void CountingMazeBuilder::BuildDoor (int, int) {
    _doors++;
}

void CountingMazeBuilder::GetCounts (
    int& rooms, int& doors
) const {
    rooms = _rooms;
    doors = _doors;
}

这里是client使用CoutingMazeBuilder的方式:

int rooms, doors;
MazeGame game;
CountingMazeBuilder builder;

game.CreateMaze(builder);
builder.GetCounts(rooms, doors);

cout << "The maze has "
    << rooms << " rooms and "
    << doors << " doors" << endl;

Known Uses(熟知的应用)

RTF转换器应用来自ET++ [WGM88]. 它的文本建造块使用builder执行以RTF格式的文本存储。
在Smalltalk-80 [Par90]中,Builder是一个常用的模式:
+ 编译器子系统中的解析类是一个以ProgramNodeBuilder对象作为参数的Director.解析对象每次识别出一个语法结构时通知ProgramNodeBuilder对象。当解析完成,它向builder请求它建造的解析树并将其返回给client.
+ ClassBuilder是Classes用来为自身创建子类的builder.在该例子中一个Class既是Director又是Product.
+ ByteCodeStream是一个以byte数组的形式创建编译后方法的builder.ByteCodeStream是对Builder模式的非标准应用,因为它建造的复杂对象被编码成一个byte数组,而不是一个正常的SmallTalk对象。但是ByteCodeStream的接口是一个典型的builder,并且我们很容易使用一个将程序表示为复合对象的不同类代替ByteCodeStream.

来自Adaptive Communications Environment的服务配置框架使用builder构造运行时连接服务器的网络服务组件。该组件的描述使用经LALR解析器解析的配置型语言。该解析器的语义行为执行该builder上的操作,将信息添加到服务组件。在该例中,解析器是个Director.

抽象工厂也可以构建复杂对象,这一点与Builder类似。主要的不同在于Builder模式侧重于一步一步地构建复杂对象。而抽象工厂的重点在于product对象(或简单或复杂)的系列(families)。Builder是在最后一步返回product,但就抽象工厂而言,product是立即返回的。
builder建造的通常是一个Composite

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/KevinsCSDN/article/details/79090512

智能推荐

mybatis插入数据显示ORA-00984: 列在此处不允许_醉耕的博客-程序员信息网

错误如图: 执行的语句是: 错误原因:插入的数据小米使用的双引号,改为单引号就行了。但是不知道为什么,如果有知道的请告知。

nvidia-jetsion tx2 上安装torch torchvision 遇到的一些坑_tx2 torchvision不兼容_xieshangxin的博客-程序员信息网

1、直接 pip install torch ,直接安装了torch v1.8.1,但是不能使用GPUtorch.cuda.is_available() 结果为false2、安装torch v1.8.0.whl【git clone下载】,然后 pip install torchvision,安装ttorchvision0.9.1成功,并且导入成功,但是在函数调用时报错。3、git clone torchvision 重新安装0.9.0,源码安装成功【setup.py】,但是 查看torchvi.

SqlServer从外部获取存储过程执行结果集_sql server 存储过程获取调用存储过程的结果集_三年丿的博客-程序员信息网

SqlServer中获取存储过程结果集No11.先放到临时表,再查,临时表结构需提前建立CREATE TABLE #TEMPINSERT INTO #TEMPEXEC [SP_Test]No22.直接存储过程中使用全局临时表暂存数据外部也可以获取

SPI--读写串行FLASH_读写串行nand flash_谷爱林的博客-程序员信息网

SPI串行读写FLASH全双工,片选信号由NSS引脚输入,低电平选定,每个从设备都有一个独立的NSS线,不能共用。MOSI(master output slave input):输出引脚MISO(master input slave output);输入引脚CPOL\CPHA及通讯模式

struct vm_area_struct linux虚拟内存实现细节_weixin_33716154的博客-程序员信息网

2019独角兽企业重金招聘Python工程师标准&gt;&gt;&gt; ...

struct vm_struct和struct vm_area_struct_stan_linux的博客-程序员信息网

内存映射信息放在vma参数中,注意,这里的vma的数据类型是struct vm_area_struct,它表示的是一块连续的虚拟地址空间区域,在函数变量声明的地方,我们还看到有一个类似的结构体struct vm_struct,这个数据结构也是表示一块连续的虚拟地址空间区域,那么,这两者的区别是什么呢?在Linux中,struct vm_area_struct表示的虚拟地址是给进程使用的,而stru

随便推点

wsl调试linux内核,Win10 Bash/WSL调试Linux环境下的.NET Core应用程序_weixin_39721924的博客-程序员信息网

一、简介使用过Mac OS的程序员都知道,在Mac Book Pro上写程序是一件比较爽的事儿,作为dotneter,我们都比较羡慕Mac系统的环境,比如命令行,当然设备也是挺漂亮的。在新的Win10系统中微软给我们提供了一个基于Ubuntu的Linux子系统(Bash/WSL)。要全用Bash/WSL也比较简单,首先要先打开开发者模式( 设置 → 更新和安全 → 针对开发人员 → 开发人员模式)...

Quartz 使用(二) — quartz.properties配置_quartz.properties配置多个_zl_momomo的博客-程序员信息网

 quartz.properties#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore#============================================================================# Configure ThreadPool (与spring整合后,使用spr...

linux驱动编写(总结篇)_linux 驱动编写_嵌入式-老费的博客-程序员信息网

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】01、linux驱动编写(入门)02、linux驱动编写(虚拟字符设备编写)03、linux驱动编写(字符设备编写框架)04、linux驱动编写(Kconfig文件和Makefile文件)05、linux驱动编写(块设备驱动代码)06、linux驱动编写(platfo......

规则学习_dlphay的博客-程序员信息网

知识图谱与基本概念基本概念规则学习定义:从训练数据中学习出一组能用于对未见示例进行判别的规则。规则定义:规则一般是:语义明确、能描述数据分布所隐含的客观规律或领域概念。逻辑规则定义:⊕←????1⋀????2⋀????3…⋀????????⊕←f_1⋀f_2⋀f_3…⋀f_L 右侧为规则体:由L个逻辑文字组成的合取式。 左侧为规则头:逻辑文字组成的目标类别或概念。规则集:若干个逻辑规则组成的...

互联网时代,帮你打开视野的20个网站!(精华版)_代码讲故事的博客-程序员信息网

哔哩哔哩地址:https://www.bilibili.com/介绍:被粉丝们亲切的称为“B站”, 现为中国年轻世代高度聚集的文化社区和视频平台. B站早期是一个ACG(动画、漫画、游戏)内容创作与分享的视频网站。经过十年多的发展,围绕用户、创作者和内容,构建了一个源源不断产生优质内容的生态系统,B站已经涵盖7000多个兴趣圈层的多元文化社区。直播方面:B站推出的国内首家关注ACG直播的互动平台,内容有趣、活动丰富、玩法多样,并向电竞、生活、娱乐领域不断延伸;游戏方面:B站是国内重要的二次元游戏分发..

推荐文章

热门文章

相关标签