chromium的设计模式之 builder pattern 构建模式
很好的利用了build设计模式。
这个类是chromium根据pdl接口文件自动生成。
1 Layer类,调用示例如下:
std::unique_ptr<protocol::LayerTree::Layer> layer_object = protocol::LayerTree::Layer::create() .setLayerId(IdForLayer(layer)) .setOffsetX(0) .setOffsetY(0) .setWidth(layer->bounds().width()) .setHeight(layer->bounds().height()) .setPaintCount(layer->debug_info() ? layer->debug_info()->paint_count : 0) .setDrawsContent(draws_content) .build(); if (skp64.length() > 0) layer_object->setSkp(skp64);
这种builder模式实现使用起来更加流畅,也叫Fluent Builder,这种实现方法我把它称为「连续构建法」。见文末参考
2 说明
本来像创建个 Layer 对象。先调用类的static方法create,生成内部类 LayerBuilder。LayerBuilder里面有个成员变量存放 new Layer().
2.1 Layer对象是通过静态方法create()生成。
static LayerBuilder<0> create() { return LayerBuilder<0>(); }
- create方法又会利用Layer
嵌套类(内部类)
LayerBuilder的来new Layer(),并把Layer对象放到了其内部,成为一个layer成员。
friend class Layer; LayerBuilder() : m_result(new Layer()) { }
LayerBuilder和Layer都隐藏了他们自己的构造器。嵌套类其实就是两个并行的类,只是把它们组织在了一起。
(由于LayerBuilder要调用Layer的私有构造,所以在LayerBuilder中宣称了Layer是自己的友元类。)
在LayerBuilder中将Layer类作为自己的友元,目的是让layer可以访问LayerBuilder的私有成员。即将自己的私有类中,开通了一个白名单,表示Layer可以访问自己的私有类。由于Layer类中的静态方法create要访问LayerBuilder的私有构造函数。
static LayerBuilder<0> create() { return LayerBuilder<0>();//这是LayerBuilder的私有的构造函数,layer本来不能访问,但是在LayerBuilder中将layer申明为友元了。 }
貌似内部类可以访问外部类的私有部分。因为LayerBuilder类中访问了外部类Layer的私有构造函数。Layer类并没有申明LayerBuilder是友元。是的。
内部类就是外部类的友元类。 内部类可以通过外部类的对象参数来访问外部类中的所有成员,但是外部类不是内部类的友元。(可以想象成铁扇公主和肚子里的孙悟空。老孙是内部类,外部的都能访问。而铁扇公主是外部类,不能访问老孙内部。)
参考
create后生成了LayerBuilder对象,调用其set方法,都是间接调用到了内部存放的成员Layer对象m_result 的set方法。最后通过build方法:
std::unique_ptr<Layer> build() { static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); return std::move(m_result); }
将最终我们要的Layer对象通过右值返回,给了智能指针。
2.2 位运算与属性值
另外一个好玩的地方是Layer某些属性是必须要的,利用了位运算。某个属性赋值后,那个位会被置1.
enum { NoFieldsSet = 0, LayerIdSet = 1 << 1, OffsetXSet = 1 << 2, OffsetYSet = 1 << 3, WidthSet = 1 << 4, HeightSet = 1 << 5, PaintCountSet = 1 << 6, DrawsContentSet = 1 << 7, AllFieldsSet = (LayerIdSet | OffsetXSet | OffsetYSet | WidthSet | HeightSet | PaintCountSet | DrawsContentSet | 0)};
函数:
LayerBuilder<STATE | LayerIdSet>& setLayerId(const String& value) { static_assert(!(STATE & LayerIdSet), "property layerId should not be set yet"); m_result->setLayerId(value); return castState<LayerIdSet>(); }
3 参考类完整实现:
一个更简洁的实例:
E:\dev\chromium\src\out\Default\gen\content\browser\devtools\protocol\page.h
class CONTENT_EXPORT ScreencastFrameMetadata : public ::crdtp::ProtocolObject<ScreencastFrameMetadata> { public: ~ScreencastFrameMetadata() override { } double GetOffsetTop() { return m_offsetTop; } void SetOffsetTop(double value) { m_offsetTop = value; } double GetPageScaleFactor() { return m_pageScaleFactor; } void SetPageScaleFactor(double value) { m_pageScaleFactor = value; } double GetDeviceWidth() { return m_deviceWidth; } void SetDeviceWidth(double value) { m_deviceWidth = value; } double GetDeviceHeight() { return m_deviceHeight; } void SetDeviceHeight(double value) { m_deviceHeight = value; } double GetScrollOffsetX() { return m_scrollOffsetX; } void SetScrollOffsetX(double value) { m_scrollOffsetX = value; } double GetScrollOffsetY() { return m_scrollOffsetY; } void SetScrollOffsetY(double value) { m_scrollOffsetY = value; } bool HasTimestamp() { return m_timestamp.isJust(); } double GetTimestamp(double defaultValue) { return m_timestamp.isJust() ? m_timestamp.fromJust() : defaultValue; } void SetTimestamp(double value) { m_timestamp = value; } template<int STATE> class ScreencastFrameMetadataBuilder { public: enum { NoFieldsSet = 0, OffsetTopSet = 1 << 1, PageScaleFactorSet = 1 << 2, DeviceWidthSet = 1 << 3, DeviceHeightSet = 1 << 4, ScrollOffsetXSet = 1 << 5, ScrollOffsetYSet = 1 << 6, AllFieldsSet = (OffsetTopSet | PageScaleFactorSet | DeviceWidthSet | DeviceHeightSet | ScrollOffsetXSet | ScrollOffsetYSet | 0)}; ScreencastFrameMetadataBuilder<STATE | OffsetTopSet>& SetOffsetTop(double value) { static_assert(!(STATE & OffsetTopSet), "property offsetTop should not be set yet"); m_result->SetOffsetTop(value); return castState<OffsetTopSet>(); } ScreencastFrameMetadataBuilder<STATE | PageScaleFactorSet>& SetPageScaleFactor(double value) { static_assert(!(STATE & PageScaleFactorSet), "property pageScaleFactor should not be set yet"); m_result->SetPageScaleFactor(value); return castState<PageScaleFactorSet>(); } ScreencastFrameMetadataBuilder<STATE | DeviceWidthSet>& SetDeviceWidth(double value) { static_assert(!(STATE & DeviceWidthSet), "property deviceWidth should not be set yet"); m_result->SetDeviceWidth(value); return castState<DeviceWidthSet>(); } ScreencastFrameMetadataBuilder<STATE | DeviceHeightSet>& SetDeviceHeight(double value) { static_assert(!(STATE & DeviceHeightSet), "property deviceHeight should not be set yet"); m_result->SetDeviceHeight(value); return castState<DeviceHeightSet>(); } ScreencastFrameMetadataBuilder<STATE | ScrollOffsetXSet>& SetScrollOffsetX(double value) { static_assert(!(STATE & ScrollOffsetXSet), "property scrollOffsetX should not be set yet"); m_result->SetScrollOffsetX(value); return castState<ScrollOffsetXSet>(); } ScreencastFrameMetadataBuilder<STATE | ScrollOffsetYSet>& SetScrollOffsetY(double value) { static_assert(!(STATE & ScrollOffsetYSet), "property scrollOffsetY should not be set yet"); m_result->SetScrollOffsetY(value); return castState<ScrollOffsetYSet>(); } ScreencastFrameMetadataBuilder<STATE>& SetTimestamp(double value) { m_result->SetTimestamp(value); return *this; } std::unique_ptr<ScreencastFrameMetadata> Build() { static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); return std::move(m_result); } private: friend class ScreencastFrameMetadata; ScreencastFrameMetadataBuilder() : m_result(new ScreencastFrameMetadata()) { } template<int STEP> ScreencastFrameMetadataBuilder<STATE | STEP>& castState() { return *reinterpret_cast<ScreencastFrameMetadataBuilder<STATE | STEP>*>(this); } std::unique_ptr<protocol::Page::ScreencastFrameMetadata> m_result; }; static ScreencastFrameMetadataBuilder<0> Create() { return ScreencastFrameMetadataBuilder<0>(); } private: DECLARE_SERIALIZATION_SUPPORT(); ScreencastFrameMetadata() { m_offsetTop = 0; m_pageScaleFactor = 0; m_deviceWidth = 0; m_deviceHeight = 0; m_scrollOffsetX = 0; m_scrollOffsetY = 0; } double m_offsetTop; double m_pageScaleFactor; double m_deviceWidth; double m_deviceHeight; double m_scrollOffsetX; double m_scrollOffsetY; Maybe<double> m_timestamp; };
class CORE_EXPORT Layer : public ::crdtp::ProtocolObject<Layer> { public: ~Layer() override { } String getLayerId() { return m_layerId; } void setLayerId(const String& value) { m_layerId = value; } bool hasParentLayerId() { return m_parentLayerId.isJust(); } String getParentLayerId(const String& defaultValue) { return m_parentLayerId.isJust() ? m_parentLayerId.fromJust() : defaultValue; } void setParentLayerId(const String& value) { m_parentLayerId = value; } bool hasBackendNodeId() { return m_backendNodeId.isJust(); } int getBackendNodeId(int defaultValue) { return m_backendNodeId.isJust() ? m_backendNodeId.fromJust() : defaultValue; } void setBackendNodeId(int value) { m_backendNodeId = value; } double getOffsetX() { return m_offsetX; } void setOffsetX(double value) { m_offsetX = value; } double getOffsetY() { return m_offsetY; } void setOffsetY(double value) { m_offsetY = value; } double getWidth() { return m_width; } void setWidth(double value) { m_width = value; } double getHeight() { return m_height; } void setHeight(double value) { m_height = value; } bool hasTransform() { return m_transform.isJust(); } protocol::Array<double>* getTransform(protocol::Array<double>* defaultValue) { return m_transform.isJust() ? m_transform.fromJust() : defaultValue; } void setTransform(std::unique_ptr<protocol::Array<double>> value) { m_transform = std::move(value); } bool hasAnchorX() { return m_anchorX.isJust(); } double getAnchorX(double defaultValue) { return m_anchorX.isJust() ? m_anchorX.fromJust() : defaultValue; } void setAnchorX(double value) { m_anchorX = value; } bool hasAnchorY() { return m_anchorY.isJust(); } double getAnchorY(double defaultValue) { return m_anchorY.isJust() ? m_anchorY.fromJust() : defaultValue; } void setAnchorY(double value) { m_anchorY = value; } bool hasAnchorZ() { return m_anchorZ.isJust(); } double getAnchorZ(double defaultValue) { return m_anchorZ.isJust() ? m_anchorZ.fromJust() : defaultValue; } void setAnchorZ(double value) { m_anchorZ = value; } int getPaintCount() { return m_paintCount; } void setPaintCount(int value) { m_paintCount = value; } bool getDrawsContent() { return m_drawsContent; } void setDrawsContent(bool value) { m_drawsContent = value; } bool hasInvisible() { return m_invisible.isJust(); } bool getInvisible(bool defaultValue) { return m_invisible.isJust() ? m_invisible.fromJust() : defaultValue; } void setInvisible(bool value) { m_invisible = value; } bool hasScrollRects() { return m_scrollRects.isJust(); } protocol::Array<protocol::LayerTree::ScrollRect>* getScrollRects(protocol::Array<protocol::LayerTree::ScrollRect>* defaultValue) { return m_scrollRects.isJust() ? m_scrollRects.fromJust() : defaultValue; } void setScrollRects(std::unique_ptr<protocol::Array<protocol::LayerTree::ScrollRect>> value) { m_scrollRects = std::move(value); } bool hasStickyPositionConstraint() { return m_stickyPositionConstraint.isJust(); } protocol::LayerTree::StickyPositionConstraint* getStickyPositionConstraint(protocol::LayerTree::StickyPositionConstraint* defaultValue) { return m_stickyPositionConstraint.isJust() ? m_stickyPositionConstraint.fromJust() : defaultValue; } void setStickyPositionConstraint(std::unique_ptr<protocol::LayerTree::StickyPositionConstraint> value) { m_stickyPositionConstraint = std::move(value); } template<int STATE> class LayerBuilder { public: enum { NoFieldsSet = 0, LayerIdSet = 1 << 1, OffsetXSet = 1 << 2, OffsetYSet = 1 << 3, WidthSet = 1 << 4, HeightSet = 1 << 5, PaintCountSet = 1 << 6, DrawsContentSet = 1 << 7, AllFieldsSet = (LayerIdSet | OffsetXSet | OffsetYSet | WidthSet | HeightSet | PaintCountSet | DrawsContentSet | 0)}; LayerBuilder<STATE | LayerIdSet>& setLayerId(const String& value) { static_assert(!(STATE & LayerIdSet), "property layerId should not be set yet"); m_result->setLayerId(value); return castState<LayerIdSet>(); } LayerBuilder<STATE>& setParentLayerId(const String& value) { m_result->setParentLayerId(value); return *this; } LayerBuilder<STATE>& setBackendNodeId(int value) { m_result->setBackendNodeId(value); return *this; } LayerBuilder<STATE | OffsetXSet>& setOffsetX(double value) { static_assert(!(STATE & OffsetXSet), "property offsetX should not be set yet"); m_result->setOffsetX(value); return castState<OffsetXSet>(); } LayerBuilder<STATE | OffsetYSet>& setOffsetY(double value) { static_assert(!(STATE & OffsetYSet), "property offsetY should not be set yet"); m_result->setOffsetY(value); return castState<OffsetYSet>(); } LayerBuilder<STATE | WidthSet>& setWidth(double value) { static_assert(!(STATE & WidthSet), "property width should not be set yet"); m_result->setWidth(value); return castState<WidthSet>(); } LayerBuilder<STATE | HeightSet>& setHeight(double value) { static_assert(!(STATE & HeightSet), "property height should not be set yet"); m_result->setHeight(value); return castState<HeightSet>(); } LayerBuilder<STATE>& setTransform(std::unique_ptr<protocol::Array<double>> value) { m_result->setTransform(std::move(value)); return *this; } LayerBuilder<STATE>& setAnchorX(double value) { m_result->setAnchorX(value); return *this; } LayerBuilder<STATE>& setAnchorY(double value) { m_result->setAnchorY(value); return *this; } LayerBuilder<STATE>& setAnchorZ(double value) { m_result->setAnchorZ(value); return *this; } LayerBuilder<STATE | PaintCountSet>& setPaintCount(int value) { static_assert(!(STATE & PaintCountSet), "property paintCount should not be set yet"); m_result->setPaintCount(value); return castState<PaintCountSet>(); } LayerBuilder<STATE | DrawsContentSet>& setDrawsContent(bool value) { static_assert(!(STATE & DrawsContentSet), "property drawsContent should not be set yet"); m_result->setDrawsContent(value); return castState<DrawsContentSet>(); } LayerBuilder<STATE>& setInvisible(bool value) { m_result->setInvisible(value); return *this; } LayerBuilder<STATE>& setScrollRects(std::unique_ptr<protocol::Array<protocol::LayerTree::ScrollRect>> value) { m_result->setScrollRects(std::move(value)); return *this; } LayerBuilder<STATE>& setStickyPositionConstraint(std::unique_ptr<protocol::LayerTree::StickyPositionConstraint> value) { m_result->setStickyPositionConstraint(std::move(value)); return *this; } std::unique_ptr<Layer> build() { static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); return std::move(m_result); } private: friend class Layer; LayerBuilder() : m_result(new Layer()) { } template<int STEP> LayerBuilder<STATE | STEP>& castState() { return *reinterpret_cast<LayerBuilder<STATE | STEP>*>(this); } std::unique_ptr<protocol::LayerTree::Layer> m_result; }; static LayerBuilder<0> create() { return LayerBuilder<0>(); } private: DECLARE_SERIALIZATION_SUPPORT(); Layer() { m_offsetX = 0; m_offsetY = 0; m_width = 0; m_height = 0; m_paintCount = 0; m_drawsContent = false; } String m_layerId; Maybe<String> m_parentLayerId; Maybe<int> m_backendNodeId; double m_offsetX; double m_offsetY; double m_width; double m_height; Maybe<protocol::Array<double>> m_transform; Maybe<double> m_anchorX; Maybe<double> m_anchorY; Maybe<double> m_anchorZ; int m_paintCount; bool m_drawsContent; Maybe<bool> m_invisible; Maybe<protocol::Array<protocol::LayerTree::ScrollRect>> m_scrollRects; Maybe<protocol::LayerTree::StickyPositionConstraint> m_stickyPositionConstraint; };
参考:
https://mp.weixin.qq.com/s/FJ_I-eS6xjWVuqApHFCupw
C++ DP.14 Builder
原创 cpluspluser CPP编程客 2021-08-27 18:29
图片
关于创建型模式,断断续续,已近尾声。
我们说:为了应对类型的变化,将对象的创建进行抽象、提炼、总结,就产生了一批创建型模式。这包括专注于「只能产生一次的对象」的Singleton,以及「以统一的接口产生对象」的Factory系列。
而本篇的Builder,则针对于创建「包含多个部分的复杂对象」。
若从接口上看,Singleton往往将创建接口命名为"instance",Factory系列往往将其命名为"create",而Builder则往往命名为"build"。
14.1
何谓复杂对象?
若一个对象由多个组成部分构成,创建之时,需要同时构建这些组成部分,就称其为复杂对象。
举个例子,一本书、一件商品属于简单对象,因为它们单独构成了一个整体;而一座建筑属于复杂对象,因为它是由门、窗、房间这些元素构成的整体。
当试图抽象创建接口时,书、商品都可以通过Factory Method直接创建,而建筑不同,你无法一次性以一个整体的形式进行创建,只有所有组成要素构建完毕,这个整体才能构建完成。
由于复杂对象的这些构成部分创建没有一个明确的顺序,因此最终构成的整体也会形式多样。就好似房子都有门、窗、房间,但设计的位置不同,最终的形式也不同。
所以,不同的构建顺序,将会产生不同形式的复杂对象。
此外,因为要依次构建这些组成部分,就无法简单地提供一个创建接口,若将这些组成部分的构建行为也放在复杂对象本身当中,自是十分冗余,不易扩展的。
14.2
分离构建产生的多样性表示
Builder模式的解决办法,就是将构建行为直接分离出去,于是构建行为就被抽象成了一个单独的类[SRP]。
此即所谓之复杂对象的「构建」与「表示」分离。
不同的构建方式可以产生不同的效果,因此根据不同的原理进行分类,分离出来的「构建行为」本身又可以拆分成不同形式。
具体来说,建筑可以由最基本的「实用」进行构建,产生的就是普通的住宅;也可以根据「自然风」进行构建,如日本以木材为主的这种房屋风格;也可以根据「人文」进行构建,如中国古代建筑的风格;也可以根据「功能」进行构建,如商业房、酒店、冒险屋。
那么此时「构建」便可作为一个抽象类,不同形式的构建行为作为具体类,这就是题目中所说的「多样性表示」。
由于构建与表示分离,所以构建具备了更好的灵活性和扩展性[OCP]。
14.3
单一复杂对象的构建法
对于理解Builder模式,游戏地图是个不错的例子。
先说一下什么是单一复杂对象?
这指的是不考虑「多样性」构建的复杂对象,那么构建行为也就没有必要作为一个抽象类,因为本身就只有一个构建行为,它本身就是具体类。
游戏地图上会包含许多对象,如树、石头、怪物等等,这就是一个复杂对象。当游戏只有一个地图的情况下,地图就是一个单一复杂对象。
现在假设地图很简单,只包含树和石头两种对象,写出基本代码:
1class Map {
2public:
3
4 void AddTree(std::shared_ptr
5 tree_ = tree;
6 }
7
8 void AddStone(std::shared_ptr
9 stone_ = stone;
10 }
11
12 void Output() const {
13 tree_->print();
14 stone_->print();
15 }
16
17private:
18 std::shared_ptr
19 std::shared_ptr
20};
因为创建地图,伴随着也要创建Tree和Stone,所以将构建行为分离出去,构成了MapBuilder类:
1class MapBuilder {
2public:
3 auto MakeTree() const {
4 return std::make_shared
5 }
6
7 auto MakeStone() const {
8 return std::make_shared
9 }
10
11 std::unique_ptr
这个Builder的任务,就是负责Map的创建工作。所有Map需要的元素,都在此处进行创建、组装,最后通过build()一次性返回创建完成的Map。
build()接口负责将构成元素组合的工作,实现如下:
1std::unique_ptr
现在便可通过Builder创建Map,
1int main() {
2 MapBuilder builder;
3 auto map = builder.build();
4 map->Output();
5}
这里Output()只是输出的Tree与Map的名字,用于查看创建结果,代码省略。
通过这种方式,使得在使用接口时格外方便,具体的创建细节都隐藏到了Builder中。
但是当前的代码,并不能阻止用户直接创建Map,怎么办呢?拒绝直接构造。
具体方法就是私有化构造函数,代码如下:
1class Map {
2 std::shared_ptr
3 std::shared_ptr
4
5 void AddTree(std::shared_ptr
6 tree_ = tree;
7 }
8
9 void AddStone(std::shared_ptr
10 stone_ = stone;
11 }
12
13 // 隐藏构造函数,防止用户手动构造
14 Map() = default;
15
16public:
17 void Output() const {
18 tree_->print();
19 stone_->print();
20 }
21
22 Map(const Map& other)
23 : tree_{ other.tree_ },
24 stone_{ other.stone_ }
25 {}
26
27 Map& operator=(const Map& other) {
28 if (this == &other) return *this;
29
30 tree_ = other.tree_;
31 stone_ = other.stone_;
32 }
33
34 friend class MapBuilder;
35};
不止构造函数,所有构成元素全部隐藏,这样用户在使用Map之时,就看不到这些细节。
隐藏构造函数之后,需要再把赋值构造写出来,因为接收Builder创建的Map时,需要使用赋值构造。
这里的一个关键点是,由于隐藏了构造,Builder也无法创建Map,所以在第34行通过友元将MapBuilder加入白名单。
14.4
连续构建法
上节内容介绍的就是典型的Builder实作法,也就是[DP]上的实作法。
然而为了可选地构建每一组成部分,以及隐藏掉当前的MapBuilder类,使得用户只需使用Map这一个类。出现了另一种实作法,这种实现使用起来更加流畅,也叫Fluent Builder,这种实现方法我把它称为「连续构建法」。
下面来看具体实现的思路与步骤。
第一步,在Map中为MapBuilder提供一个静态工厂方法,目的是为了直接从Map创建MapBuilder。
代码如下:
1class Map {
2 // same as above...
3
4public:
5 static MapBuilder Builder() {
6 return MapBuilder{};
7 }
8};
通过Builder这个工厂函数,就达到了隐藏MapBuilder的作用。
第二步,修改MapBuilder接口,代码如下:
1class MapBuilder {
2public:
3 MapBuilder& MakeTree() {
4 map->AddTree(std::make_shared
5 return *this;
6 }
7
8 MapBuilder& MakeStone() {
9 map->AddStone(std::make_shared
10 return *this;
11 }
12
13 std::unique_ptr
14 return std::move(map);
15 }
16
17private:
18 std::unique_ptr
19};
因为要支持可选地构建组成元素,所以构建每个组成元素,都进行组装Map,当调用build()时,其实已经组装完成,直接返回便可。
连续构建法的关键在于,在每一个构成部分构建完成之后,都以引用的形式返回当前的Builder。这样,就可以连续构建每一个组成元素。
连续构建法实现的Builder在各种库中十分流行,使用形式如下:
1int main() {
2 auto map = Map::Builder()
3 .MakeTree()
4 .MakeStone()
5 .build();
6}
14.5
多样性复杂对象的构建法
看完了单一复杂对象的构建法,下面来看多样性复杂对象的构建法。
多样性意味着构建行为存在多种形式,比如说对游戏地图扩展,分为一个城市地图,和一个雪谷地图。那么树与石头在这两个地图中将有不同的表现形式,它们在雪谷表面可能会覆上一层雪,城市中则是正常形式。
那么第一步,先稍微修改下Tree与Stone的接口,以便区分不同的地图,代码如下:
1// class Tree
2struct Tree {
3 Tree() = default;
4 Tree(const char* str) : str_{str} {}
5
6 void print() {
7 std::cout << "This is a(n) " << str_ << std::endl;
8 }
9
10private:
11 const char* str_;
12};
13
14// class Stone
15struct Stone {
16 Stone() = default;
17 Stone(const char* str) : str_{ str } {}
18
19 void print() {
20 std::cout << "This is a(n) " << str_ << std::endl;
21 }
22
23private:
24 const char* str_;
25};
根据构造函数传递参数,可以让我们区分不同地图产生的对象。
第二步,定义抽象的构建行为类,定义如下:
1class MapBuilderBase {
2public:
3 virtual MapBuilderBase& MakeTree() { return *this; }
4 virtual MapBuilderBase& MakeStone() { return *this; }
5
6 auto build() {
7 return std::move(map);
8 }
9
10protected:
11 std::unique_ptr
12};
为每一个构成元素定义生成接口,这和前面一样。
不过此处定义成什么都不干的虚函数,不定义为纯虚形式,是因为前面说过,生成哪个部件不是硬性要求,应该可以选择性地产生。这样一来,具体类就可以只定义感兴趣的部件。
最终构建Map的build()函数,因为属于公共行为,于是也应当定义在抽象类中。
第三步,定义具体的构建行为类,来简单看下雪谷地图的代码:
1class SnowHollowMapBuilder : public MapBuilderBase {
2public:
3
4 MapBuilderBase& MakeTree() override {
5 map->AddTree(std::make_shared
6 return *this;
7 }
8
9
10 MapBuilderBase& MakeStone() override {
11 map->AddStone(std::make_shared
12 return *this;
13 }
14};
这里形式与单一复杂对象相似,便不再细论。
第四步就是将这些类作为Map的友元,因为它们都需要访问Map的私有元素。然后,定义连续性访问接口,代码如下:
1static CityMapBuilder CityBuilder() {
2 return CityMapBuilder{};
3}
4
5static SnowHollowMapBuilder SnowHollowBuilder() {
6 return SnowHollowMapBuilder{};
7}
最后,便可使用Builder构建多样性Map了。使用和前面一样:
1auto map = Map::CityBuilder()
2 .MakeTree()
3 .MakeStone()
4 .build();
14.6
组合构建法
另一种多样性复杂对象拆分形式和以上不同,它们的构建元素存在交叉。
什么意思呢?
在以上的示例中,两种地图都存在MakeTree()和MakeStone()两个接口,然而你无法同时产生地图一与地图二的元素,比如无法同时出现积雪的树与正常的石头。
这种「元素乱入」的产生方式可以形成更加有新意的效果,某些时候你或许正需要这种效果,便可以考虑这种「组合构建法」。
此外,并没有要求说复杂对象的多样性一定要一致,它也可以是完全不重复的多样性。
比如一个地图是陆地,一个地图是海洋,此时二者当中的构成元素可以完全不一样。那么你若是使用组合构建法,便可以产生一个充满颠覆性的创新,形成一个奇幻的地图。当然,这只是一个例子。
顺便一提,这种构成元素不重复的组合构建法,还可以用来表示结构化信息。什么意思呢?比如组织一场活动,活动包含时间、地点、参加人员、事情与所需物品,那么组合构建法就可以这样表示:
1auto activity = Activity::Builder()
2 .when()
3 .date("2021/08/30")
4 .where()
5 .at("学校会议室")
6 .who()
7 .involve("老师")
8 .involve("职工")
9 .why()
10 .about("学生报名工作安排")
11 .about("教学安排")
12 .what()
13 .arrange("迎接人员")
14 .prepare("校园卡")
15 .purchase("课程书籍");
可以看到,一场非常复杂的活动都可以通过这种构建法轻松表示,代码如诗,诚不我欺:D
当然,这里主要还是想告诉大家,Builder模式的构建能力其实非常强大。它可以产生小到三两元素构成的复杂对象,也可以构建大到成百上千元素构成的超复杂对象。
说了这个多,下面来看如何具体实现「组合构建法」。
要进行组合式构建,首先重构Builder基类的代码,重写如下:
1class MapBuilderBase
2{
3protected:
4 typedef Map value_type;
5 typedef Map& reference_type;
6 value_type map;
7
8public:
9
10 MapBuilderBase() = default;
11 MapBuilderBase(reference_type map)
12 : map{ map }
13 {}
14
15 auto build() {
16 return std::move(map);
17 }
18
19 CityMapBuilder city();
20 SnowHollowMapBuilder snow_hollow() const;
21};
由于要混合创建,所以先去除所有公共接口,只保留build()。
要实现连续的组合构建法(组合构建法也只能是连续的),关键就在于将所有需要组合构建的子类都抽象出一个接口定义在基类中。在第19,20行分别为城市与雪谷地图抽象了不同的接口,它们的返回值都是自己的类型。
因此,通过city()就能够获取到城市地图的构造器,通过snow_hollow()就能够获取到雪谷地图的构造器。而由于这两个接口皆处于基类之中,而CityMapBuilder和SnowHollowMapBuilder又都是基类的子类,所以二者能够相互访问。
再者,因为这两个接口会返回各自的构造器,那么最终返回的Map需要进行传递,这样才能保存每次构建的单个组成元素。这是通过向构造函数传入基类构造函数传入map完成的。
具体代码只看其中一份的便好:
1SnowHollowMapBuilder MapBuilderBase::snow_hollow() const
2{
3 return SnowHollowMapBuilder{ map };
4}
5
6class SnowHollowMapBuilder : public MapBuilderBase
7{
8public:
9 SnowHollowMapBuilder(value_type map)
10 : MapBuilderBase{map}
11 {}
12
13 SnowHollowMapBuilder& MakeTree() {
14 map.AddTree(std::make_shared
15 return *this;
16 }
17};
这里通过基类构建snow_hollow(),便返回到了SnowHollowMapBuilder对象,于是可以通过其创建积雪的树。通过当前对象又可以访问city()接口,此时便又切换到了CityMapBuilder,这就是组合构建。
还剩下一件工作,就是在Map中设置Builder()接口,代码如下:
1MapBuilderBase Map::Builder() {
2 return MapBuilderBase{};
3}
那么流程就是先得到MapBuilderBase,再通过其中的两个接口得到不同的子类,从而实现组合构建。
现在,可以通过组合构建法的实现,混合地进行构造:
1int main() {
2 auto map = Map::Builder()
3 .city().MakeStone()
4 .snow_hollow().MakeTree()
5 .build();
6}
14.7
总结
若说Factory Method用于产生同一系列的多个不同对象,Abstract Factory用于产生相互关联或相互依赖的族系对象,那Builder可以产生的对象更加广泛,更加复杂。
这也使得Builder在库的开发中相当常用,许多流行库都使用它进行复杂对象的构建。
一般来说,我们使用「连续构建法」实现Builder,对于单一复杂对象情况比较简单,不过这也很常用。多样性复杂对象构建起来略显复杂,不过也只是增加了表现形式。「组合构建法」非常灵活,可以创建非常复杂的对象,也可以表示结构化信息,但是如何规划这种组合是一个重点,分类不好恐会适得其反。
本篇信息密度依旧很高,接近8000字。不过限于篇幅与时间,还是有一些想法与优化内容没有写上,但总体来说主体是完善的。
这个系列在更新差不多了会进行二次更新迭代,到时会补充完整,再加扩展,还请大家多多支持。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?