【设计模式】建造者模式(Builder)
目录
一、问题导入
二、结构(如果只是应付考试的话,可以不细看)
三、优劣
四、另一种形式
五、个人理解
一、问题导入
我们点一个汉堡,商家按固定搭配做好,我们直接拿成品,这是工厂方法 —— 没法选肉饼、配菜,只能接受预设方案。但如果我们自己一步一步选、一步一步组合:要牛肉饼、加生菜、抹番茄酱,最后拼成汉堡,这就是建造者模式。
从中能看出,工厂方法是一步拿结果,建造者模式是多步定制、最后得结果。接下来,我们进一步探讨建造者模式。
二、结构(如果只是应付考试的话,可以不细看)
接着从上述案例出发,我们首先需要定义一个产品类hambuger。之后,提供一个抽象生成器hamburger_builder用来生成汉堡。然后,还可以提供具体生成器,用以生成固定的产品,比如用以生成安格斯牛堡的beef_hambuger_builder,让他提供固定的流程。最后,只需要再去提供一个导演类director去指导builder来生成想要的产品。
那么,他的整体结构如下:
下面,给出了相应的示例代码:
在进行使用的过程中,我们可以通过director类来获取预定好的组成部分(个人认为该种形式无法很明显地体现建造者模式和工厂模式的区别)
也可以通过直接使用CustomerBurgerBuilder来进行自定义汉堡内容
#pragma once
#include <iostream>
#include <string>// 命名空间:与你的_AbstractFactory风格保持一致
namespace _BuilderPattern
{// 1. 产品:汉堡(最终要构建的复杂对象)class Burger{private:std::string patty_; // 肉饼(成员变量用_结尾)std::string vegetable_;// 蔬菜std::string sauce_; // 酱料public:// 构造与析构(默认实现)Burger() = default;~Burger() = default;// 设置汉堡各部分(接口函数)void setPatty(const std::string& patty) { patty_ = patty; }void setVegetable(const std::string& vegetable) { vegetable_ = vegetable; }void setSauce(const std::string& sauce) { sauce_ = sauce; }// 展示汉堡组成(功能函数)void show() const{std::cout << "汉堡组成:" << patty_ << " + " << vegetable_ << " + " << sauce_ << std::endl;}};// 2. 抽象建造者:定义汉堡构建的步骤接口class BurgerBuilder{public:BurgerBuilder() = default;virtual ~BurgerBuilder() = default; // 虚析构,确保子类正确释放// 纯虚函数:分步构建(参数化,支持自定义)virtual void buildPatty(const std::string& patty) = 0;virtual void buildVegetable(const std::string& vegetable) = 0;virtual void buildSauce(const std::string& sauce) = 0;// 获取最终产品virtual Burger* getResult() = 0;};// 3. 具体建造者:实现汉堡的构建逻辑class CustomBurgerBuilder : public BurgerBuilder{private:Burger* burger_; // 持有当前构建的汉堡对象public:CustomBurgerBuilder(){burger_ = new Burger(); // 初始化空汉堡}~CustomBurgerBuilder() override{delete burger_; // 释放资源}// 实现抽象方法:设置具体配料void buildPatty(const std::string& patty) override{burger_->setPatty(patty);}void buildVegetable(const std::string& vegetable) override{burger_->setVegetable(vegetable);}void buildSauce(const std::string& sauce) override{burger_->setSauce(sauce);}// 返回构建完成的汉堡Burger* getResult() override{return burger_;}};// 4. 导演:封装预设构建流程(可选,简化常用组合)class Director{private:BurgerBuilder* builder_; // 依赖抽象建造者(多态)public:// 传入具体建造者Director(BurgerBuilder* builder) : builder_(builder) {}~Director() = default;// 预设方案1:经典牛肉汉堡void buildClassicBeefBurger(){builder_->buildPatty("炭烤牛肉饼");builder_->buildVegetable("生菜+番茄片");builder_->buildSauce("番茄酱+芥末酱");}// 预设方案2:招牌鸡肉汉堡void buildSignatureChickenBurger(){builder_->buildPatty("香煎鸡胸肉");builder_->buildVegetable("黄瓜丝+洋葱圈");builder_->buildSauce("蜂蜜沙拉酱");}};// 测试函数:验证模式功能(与你的test()风格一致)void test(){std::cout << "=== 建造者模式(汉堡案例)测试 ===" << std::endl;// 场景1:用导演快速制作预设汉堡CustomBurgerBuilder classicBuilder;Director director(&classicBuilder);director.buildClassicBeefBurger(); // 调用预设流程Burger* classicBurger = classicBuilder.getResult();std::cout << "【经典牛肉堡】" << std::endl;classicBurger->show();// 场景2:完全自定义汉堡(体现建造者的核心价值)CustomBurgerBuilder myBuilder;myBuilder.buildPatty("双层植物肉"); // 自定义肉饼myBuilder.buildVegetable("牛油果+紫甘蓝"); // 自定义蔬菜myBuilder.buildSauce("蒜香蛋黄酱"); // 自定义酱料Burger* myBurger = myBuilder.getResult();std::cout << "\n【自定义植物汉堡】" << std::endl;myBurger->show();}
}
三、优劣
优势:
(1)分离构建过程和表示形式,使构建过程更具灵活性,并允许构建不同的表示形式
(比如做汉堡时,“选配料的过程” 和 “最终汉堡的样子” 是分离的,既可以做经典牛肉堡,也可以自定义植物汉堡,灵活性很高。)
(2)可以更好地控制构建过程,并隐藏具体的构建细节
(用户只需要关心 “选什么配料”,不需要知道汉堡是怎么组装的(比如面包怎么夹、酱料怎么涂),细节被隐藏了。)
(3)代码可复用性高,同一个构建器可在不同的构建流程中复用
(同一个 CustomBurgerBuilder
既可以给导演用来做预设汉堡,也可以自己用来做自定义汉堡,不用重复写逻辑。)
劣势:
(1)若产品的属性较少,建造者模式可能会导致代码冗余。
(如果汉堡只有 “肉饼” 一个属性,用建造者模式就没必要,直接一个工厂方法更简单。)
(2)建造者模式会增加系统中类和对象的数量。
(产品、抽象建造者、具体建造者、导演,类的数量比简单工厂多,小项目可能觉得 “重”。)
四、另一种形式
建造者模式还有另一种表达形式,那就是在构造函数当中提供所有的预设值,而在其每一个设置属性的函数当中,其设置返回值为当前对象自身(this),从而在进行对象的实例化的时候,通过链式调用来进行初始化。
不过,这种写法将会导致类的构造函数十分冗杂,我个人并不喜欢这种表达形式。
五、个人理解
建造者模式实际上也是一种创建对象的方式,就工厂模式而言,我们可以获得一个固定的产品。但是,当产品的属性变得多样且不可控的时候,他们的整体制作流程往往不需要改变,那么,用户就只需要考虑单个组件如何进行选择即可。
就比如常见的换装游戏,我们自然可以预先设定好几套风格的衣服供玩家自行选择(古风还是现代风的)。但是,往往我们也需要“打开衣橱”,让玩家自行一一挑选。不过,不管我们先选头发还是先选帽子,其渲染的结果始终是先进行头发的渲染,后进行帽子的渲染,从而确保帽子覆盖在头发上面,这便是固化的流程,其对于用户来说是看不到的。(这是我能够想到的一个比较典型且契合的应用。)