技术标签: Builder 模式 译文 建造者模式 设计模式
将一个复杂对象的构建(construction)与其表示(representation)分离,这样同样的构建过程可以创建出不同的表示。
一个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文件,根据不同的标签,将文本表示成不同形式。这里也是根据不同标记使用不同的转换器转换文本格式。
每种转换器类都为创建和装配一个复杂对象提供机制,并将其隐藏在一个抽象接口中。转换器与阅读器隔离,阅读器负责解析(parsing)RTF文档。
Buider
模式满足(captures)所有这些关系。在该模式中,每一个converter
(转换器)类称为一个Builder,而这个阅读器称为Director(指挥者)。应用到这个例子中,Builder
模式将翻译文本格式(也就是RTF文档的解析)这个逻辑从怎样创建和表示转换后格式中分离出来。这使得我们可以复用RTFReader
的解析逻辑从RTF文档创建出不同的文本表示——只需使用不同的TextConverter
子类配置RTFReader
以下情况使用Builder
模式:
TextConverter
) Product
对象的部件(part)的抽象接口ASCIIConverter
,TeXConverter
,TextWidgetConverter
) Builder
接口构造(constructs)和组装(assembles)产品部件GetASCIIText
,GetTextWidget
)RTFReader
) ASCIIText
, TeXText
, TextWidget
) ConcreteBuilder
建造了产品(product
)的内部表示,通过ConcreteBuilder
定义的方法组装成了它(译者注:指product
)。Director
对象并使用想要(desired)的Builder
对象配置它(译者注:它指Director
)。product
的一个部件时Director
就通知Builder
Builder
处理来自Director
的请求并向product
中添加part
。Builder
中获取产品。下面的交互图阐述了Builder
和Director
如何与客户端协作。
这里是Builder
模式的结果:
Builder
对象为director
提供一个构建product
的抽象接口。该接口允许builder
隐藏产品的表示及其内部结构。它同时也隐藏了产品的组装方式。因为产品是通过一个抽象接口构建,你要更改产品的内部表示只需定义一个新的builder
.它隔离了构建(construction)代码和表示(representation)代码。Buider
模式通过封装复杂对象的构建方式和表示方式提升了模块性。客户端无需知道有关定义product
内部结构的类的任何信息;这样的类不会出现在Builder
的接口中。
每个ConcreteBuilder
包含了创建和组装特定种类的product
的所有代码。代码只编写一次,之后不同的Director
可以复用它从同样的部件组中(the same set of parts)建造Product
变体。在先前的RTF例中,我们可以为RTF之外的格式定义一个阅读器,叫SGMLReader
,然后使用同样的TextConverter
去产生SGML文档的ASCIIText
,TeXText
和TextWidget
译文。
它让你对构建过程有更精细的控制。不像一步到位地构造product
的创建模式,Builder
模式在director
的控制下,一步一步地构建product
.只有当product
完成了,director
才会从builder
中获取它。因此相比其他创建型模式,Builder
接口更能反映product
的构建过程。这使得你对构建过程有更精细的控制,并由此控制最终的(resulting)product
的内部结构。
通常,有一个抽象的Builder
类为每一个组件定义一个操作,director
会用这个操作请求创建该组件。该操作默认情况下不做任何事。ConcreteBuilder
类根据它要创建的组件覆盖该操作。
这里是其他需要考虑的实现问题:
1. 组装和构造接口。Builder
通过一步一步的方式构建他们的product
.因此Builder
类接口必须足够全面以让所有的concrete builder
都可以构造product
。
一个关键设计问题涉及到构建和组装过程的模型。将构造请求结果简单附加到`product`这种模型通常是足够的。在RTF实例中,`builder`转换下一个`token`并将其附加到它目前已转换的文本中。
但是有时候你可能需要访问早期构造的`product`的部分。在实例代码的`Maze`(迷宫)示例中,`MazeBuilder`接口让你在已存在的房子之间添加一扇门。像自底向上构建的解析树这样的树结构也是如此。在那种情形中,`builder`要返回孩子节点给`director`,随后`director`将它们回传给`builder`从而建立父节点。
product
没有抽象类?在通常情形下,经concrete builder
产生的product
在表示上有很大差别以至于给不同的product
定义一个公有的父类没有任何好处。在RTF实例中,ASCIIText
和TextWidget
对象不可能有也不需要一个公有的接口。因为客户端通常会使用合适的concrete builder
配置director
,客户端只需要知道Builder
哪一个concrete
子类在用,并对其product
进行相应处理.Builder
中的方法默认为空实现。在C++中,build
方法有意不声明为纯虚拟成员函数,而是定义为空方法,让客户端只覆盖他们感兴趣的操作。我们将定义多种以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
操作返回maze
给client
. 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
,door
和wall
的这些类,以及这些部分是如何组装完成最终的maze
。某些人可能会想到这里有表示room
和door
的类,但是并没有看到表示wall
的类(but
there is no hint of one for walls.)。这是为了让更改maze
的表示方式更加容易,这样所有MazeBuilder
的client
都不需要更改。
与其他创建型模式一样,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
,每一种使用不同的room
、wall
和door
类。
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;
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
错误如图: 执行的语句是: 错误原因:插入的数据小米使用的双引号,改为单引号就行了。但是不知道为什么,如果有知道的请告知。
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中获取存储过程结果集No11.先放到临时表,再查,临时表结构需提前建立CREATE TABLE #TEMPINSERT INTO #TEMPEXEC [SP_Test]No22.直接存储过程中使用全局临时表暂存数据外部也可以获取
SPI串行读写FLASH全双工,片选信号由NSS引脚输入,低电平选定,每个从设备都有一个独立的NSS线,不能共用。MOSI(master output slave input):输出引脚MISO(master input slave output);输入引脚CPOL\CPHA及通讯模式
2019独角兽企业重金招聘Python工程师标准>>> ...
内存映射信息放在vma参数中,注意,这里的vma的数据类型是struct vm_area_struct,它表示的是一块连续的虚拟地址空间区域,在函数变量声明的地方,我们还看到有一个类似的结构体struct vm_struct,这个数据结构也是表示一块连续的虚拟地址空间区域,那么,这两者的区别是什么呢?在Linux中,struct vm_area_struct表示的虚拟地址是给进程使用的,而stru
一、简介使用过Mac OS的程序员都知道,在Mac Book Pro上写程序是一件比较爽的事儿,作为dotneter,我们都比较羡慕Mac系统的环境,比如命令行,当然设备也是挺漂亮的。在新的Win10系统中微软给我们提供了一个基于Ubuntu的Linux子系统(Bash/WSL)。要全用Bash/WSL也比较简单,首先要先打开开发者模式( 设置 → 更新和安全 → 针对开发人员 → 开发人员模式)...
quartz.properties#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore#============================================================================# Configure ThreadPool (与spring整合后,使用spr...
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】01、linux驱动编写(入门)02、linux驱动编写(虚拟字符设备编写)03、linux驱动编写(字符设备编写框架)04、linux驱动编写(Kconfig文件和Makefile文件)05、linux驱动编写(块设备驱动代码)06、linux驱动编写(platfo......
知识图谱与基本概念基本概念规则学习定义:从训练数据中学习出一组能用于对未见示例进行判别的规则。规则定义:规则一般是:语义明确、能描述数据分布所隐含的客观规律或领域概念。逻辑规则定义:⊕←????1⋀????2⋀????3…⋀????????⊕←f_1⋀f_2⋀f_3…⋀f_L 右侧为规则体:由L个逻辑文字组成的合取式。 左侧为规则头:逻辑文字组成的目标类别或概念。规则集:若干个逻辑规则组成的...
哔哩哔哩地址:https://www.bilibili.com/介绍:被粉丝们亲切的称为“B站”, 现为中国年轻世代高度聚集的文化社区和视频平台. B站早期是一个ACG(动画、漫画、游戏)内容创作与分享的视频网站。经过十年多的发展,围绕用户、创作者和内容,构建了一个源源不断产生优质内容的生态系统,B站已经涵盖7000多个兴趣圈层的多元文化社区。直播方面:B站推出的国内首家关注ACG直播的互动平台,内容有趣、活动丰富、玩法多样,并向电竞、生活、娱乐领域不断延伸;游戏方面:B站是国内重要的二次元游戏分发..