深入浅出设计模式——创建型模式之建造者模式
文章目录
- 建造者模式简介
- 建造者模式结构
- 建造者模式代码实例
- 定义产品类House
- 定义建造者
- 定义抽象建造者AbstractBuilder
- 定义具体建造者
- 定义指挥者
- 客户端代码示例
- 运行结果
- 建造者模式总结
代码仓库
建一栋房子总共分几步?建造者模式告诉你答案!
“把大象装冰箱,总共分几步?”
“三步。第一步,打开冰箱门;第二步,把大象装进冰箱;第三步,把冰箱门关上。”
Jungle活了这20多年,全靠这个笑话活着! 把大象装冰箱竟然只需要三步?那到底是怎么把大象装进冰箱呢?你问我,我问谁?再说,我也不关心这个呀!这……来点实际的吧,如果Jungle要建一栋房子,总共分几步?本文的建造者模式将声情并茂地向您娓娓道来……
建造者模式简介
建造者模式用于创建过程稳定,但配置多变的对象。在《设计模式》一书中的定义是:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
建造者模式将客户端与包含多个部件的复杂对象的创建过程分离,客户端不必知道复杂对象的内部组成方式与装配方式(就好像Jungle不知道到底是如何把大象装进冰箱一样),只需知道所需建造者的类型即可。
建造者模式定义:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
“同样的构建过程可以创建不同的表示”??这句话是什么意思呢?想象一下,建造一栋房子,建造过程无非都是打地基、筑墙、安装门窗等过程,但不同的客户可能希望不同的风格或者过程,最终建造出来的房子当然就呈现不同的风格啦!
经典的“建造者-指挥者”模式现在已经不太常用了,现在建造者模式主要用来通过链式调用生成不同的配置。比如我们要制作一杯珍珠奶茶。它的制作过程是稳定的,除了必须要知道奶茶的种类和规格外,是否加珍珠和是否加冰是可选的。
使用建造者模式的好处是不用担心忘了指定某个配置,保证了构建过程是稳定的。在 OkHttp、Retrofit 等著名框架的源码中都使用到了建造者模式。
建造者模式结构
建造者模式UML类图如下:
建造者模式代码实例
考虑这样一个场景,如下图:
Jungle想要建造一栋简易的房子(地板、墙和天花板),两个工程师带着各自的方案找上门来,直接给Jungle看方案和效果图。
犹豫再三,Jungle最终选定了一位工程师……交房之日,Jungle满意的看着建好的房子,
开始思考:这房子究竟是怎么建成的呢?这地板、墙和天花板是怎么建造的呢?
工程师笑着说:“It’s none of your business”
UML图如下:
定义产品类House
House是本实例中的产品,具有floor、wall和roof三个属性。
class House {
public:House() {}void setFloor(string iFloor) {this->floor = iFloor;}void setWall(string iWall) {this->wall = iWall;}void setRoof(string iRoof) {this->roof = iRoof;}// 打印House信息void printfHouseInfo() {// this->floor 是一个 std::string 类型,而C语言函数 printf 无法直接输出 std::string 类型。// 使用 .c_str() 就能将 std::string 转换为一个 const char*(C风格字符串) ,供 printf 使用。printf("Floor:%s\t\n", this->floor.c_str());printf("Wall:%s\t\n", this->wall.c_str());printf("Roof:%s\t\n", this->roof.c_str());}private:string floor;string wall;string roof;
};
定义建造者
定义抽象建造者AbstractBuilder
// 抽象建造者AbstractBuilder
class AbstractBuilder {
public:AbstractBuilder() : house(std::make_unique<House>()) {}virtual ~AbstractBuilder() = default; // 使用默认析构函数即可AbstractBuilder(const AbstractBuilder&) = delete;AbstractBuilder& operator=(const AbstractBuilder&) = delete;virtual void buildFloor() = 0;virtual void buildWall() = 0;virtual void buildRoof() = 0;virtual std::unique_ptr<House> getHouse() {return std::move(house); // 转移所有权}protected:std::unique_ptr<House> house;
};
定义具体建造者
// 具体建造者ConcreteBuilderA
// 子类无需再定义getHouse()和析构函数,因为基类已经完成这些任务。
class ConcreteBuilderA: public AbstractBuilder {
public:ConcreteBuilderA() { printf("ConcreteBuilderA\n"); }void buildFloor() override { house->setFloor("Floor_A"); }void buildWall() override { house->setWall("Wall_A"); }void buildRoof() override { house->setRoof("Roof_A"); }
};// 具体建造者ConcreteBuilderB
class ConcreteBuilderB: public AbstractBuilder {
public:ConcreteBuilderB() { printf("ConcreteBuilderB\n"); }void buildFloor() override { house->setFloor("Floor_B"); }void buildWall() override { house->setWall("Wall_B"); }void buildRoof() override { house->setRoof("Roof_B"); }
};
定义指挥者
// 指挥者Director
class Director {
public:Director(): builder(nullptr) {}// Builder的生命周期应由调用方自己管理,不能由Director删除,否则可能造成未知问题。~Director() = default; Director(const Director&) = delete;Director& operator=(const Director&) = delete;void setBuilder(AbstractBuilder* iBuilder) {this->builder = iBuilder;}std::unique_ptr<House> construct() {builder->buildFloor();builder->buildWall();builder->buildRoof();return builder->getHouse(); // 返回智能指针}private:AbstractBuilder* builder;
};
客户端代码示例
#include "BuilderPattern.h"int main() {// 抽象建造者AbstractBuilder* builder = nullptr;// 指挥者 (生命周期在main中)Director director; // 指定具体建造者Abuilder = new ConcreteBuilderA();director.setBuilder(builder);// house的所有权转移给调用方std::unique_ptr<House> houseA = director.construct();houseA->printfHouseInfo();// 注意:house内存已被外部接管,不应由builder释放delete builder; // 此时builder的析构函数应避免删除house// delete houseA; // 由调用方手动释放house内存// 指定具体建造者B (这里应改成ConcreteBuilderB)builder = new ConcreteBuilderB();director.setBuilder(builder);std::unique_ptr<House> houseB = director.construct();houseB->printfHouseInfo();delete builder; // 同上// delete houseB; // 调用方负责释放内存return 0;
}
运行结果
建造者模式总结
从客户端代码可以看到,客户端只需指定具体建造者,并作为参数传递给指挥者,通过指挥者即可得到结果。**客户端无需关心House的建造方法和具体流程。如果要更换建造风格,只需更换具体建造者即可,不同建造者之间并无任何关联,方便替换。**从代码优化角度来看,其实可以不需要指挥者Director的角色,而直接把construct方法放入具体建造者当中。
之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!