Builder Pattern
建造者模式。
考虑生活中的常见场景——餐厅订餐。该餐厅决定提供 N 中套餐 (setMenu 类描述),主要描述如下:
namespace Restaurant {
// 产品类, 此景中为 套餐(set menu)
class setMenu {
private:
std::vector<std::string> mealOrder; // 点的菜名
static std::map<const std::string, double> priceList;
public:
// 为套餐加成分
void addMeal(const std::string name) {
mealOrder.emplace_back(name);
}
// 付账单
void payBill() {
auto comsumption = 0.00;
for(const auto &it : mealOrder) {
comsumption += priceList[it];
std::cout << priceList[it] << " RMB ------ " << it << "\n";
}
std::cout << "\n本次套餐消费总共 " << comsumption << " RMB\n\n";
}
};
}
已知目标对象是套餐 setMenu, 目标效果是形成不同的套餐——很容易想到工厂模式(简单工厂模式),思路就是根据选择套餐的不同,由工厂制造相应的对象。
但是,解决问题了吗?套餐之间的不仅仅是在对象定义上,还在对象成分上,例如套餐A 组成是“米饭,牛肉,椰子汁”,套餐 B组成是 “面条,扁豆,豆浆”,所以在制造不同的对象的同时,也在制造不同对象内部的成分。
所以,现在的问题由制造套餐对象,转移到了制造套餐内部对象上了。
现在,可以想象如何利用工厂模式的思想构造各种成分了。假设,需要制作三种类型的套餐,可以考虑工厂模式,分别为 Factory_A, Factory_B, Factory_C。再假设,每种套餐都包含三个部分——主食,热菜,饮料,故每种 Factory 都要制造这三种成分,尝试抽象工厂,大致如下:
(为了区分抽象工厂,建立和 Builder Pattern 的联系,命名采用 Builder, 而不是 factory)
// 提供制造套餐不同成分的接口
class Builder {
virtual void addRice() = 0; // 选择主食
virtual void addDish() = 0; // 选择热菜
virtual void addDrinks() = 0; // 选择饮料
};
// 制造 A 套餐
class Builder_A : public Builder {
// 实现基类的接口
void addRice() {} // 制造主食对象(在例子中简化为 std::string, 下同)
void addDish() {} // 制造热菜对象
void addDrinks() {} // 制造饮料对象
} ;
// 制造 B 套餐
class Builder_B : public Builder {
// 实现基类的接口
void addRice() {} // 制造主食对象(在例子中简化为 std::string, 下同)
void addDish() {} // 制造热菜对象
void addDrinks() {} // 制造饮料对象
} ;
// 制造 C 套餐
class Builder_C : public Builder {
// 实现基类的接口
void addRice() {} // 制造主食对象(在例子中简化为 std::string, 下同)
void addDish() {} // 制造热菜对象
void addDrinks() {} // 制造饮料对象
} ;
// ...
现在,已经基本完成了不同套餐内部成分的构造,还剩下一个问题——如何返回合成之后的套餐对象?借鉴工厂模式,考虑每种不同的制造者(在上面是 Builder_A, Builder_B, BUilder_C),内部含有套餐 setMenu 的智能指针,完整建立对象之后只要返回这个指针即可。其实问题被简化了,因为这里的各种成分,我都假设为 std::string, 更实用的做法应该是不同成分继承自某个 Object, 智能指针指向的对象中存储这些对象或这些对象的引用。
目前的成果:
// 建造者模型, 提供接口, 情景含义————制作套餐
class Builder {
public:
virtual ~Builder() = default;
virtual void addRice() = 0;
virtual void addDish() = 0;
virtual void addDrinks() = 0;
virtual std::shared_ptr<setMenu> getMeal() = 0;
} ;
// 制造套餐 A
class Builder_A : public Builder {
public:
void addRice() override {
ptr->addMeal("rice");
}
void addDish() override {
ptr->addMeal("pizza");
ptr->addMeal("drumSticks");
}
void addDrinks() override {
ptr->addMeal("orange juice");
}
std::shared_ptr<setMenu> getMeal() {
return ptr;
}
private:
std::shared_ptr<setMenu> ptr = std::make_shared<setMenu>();
} ;
// 制造套餐 B
class Builder_B : public Builder {
public:
void addRice() override {
ptr->addMeal("dongBei rice");
}
void addDish() override {
ptr->addMeal("salted Duck");
}
void addDrinks() override {
ptr->addMeal("soya-bean milk");
}
std::shared_ptr<setMenu> getMeal() {
return ptr;
}
private:
std::shared_ptr<setMenu> ptr = std::make_shared<setMenu>();
} ;
// // 制造套餐 C
class Builder_C : public Builder {
public:
void addRice() override {
ptr->addMeal("Thai rice");
}
void addDish() override {
ptr->addMeal("pizza");
ptr->addMeal("beef");
}
void addDrinks() override {
ptr->addMeal("coconut milk");
}
std::shared_ptr<setMenu> getMeal() {
return ptr;
}
private:
std::shared_ptr<setMenu> ptr = std::make_shared<setMenu>();
} ;
现在,每种套餐内部所有成分的构造都可以实现,还缺少组合它们的功能,不同套餐都会经历同一个打包的过程——主食 + 热菜 + 饮料,既然是共同的功能,便可以集中到基类中。如何调用派生类函数 ?传基类指针,调用虚函数即可。打包的时机?当然是在 get 套餐之前啦!主要改变如下:
Builder 接口
// 建造者模型, 提供接口, 情景含义————制作套餐
class Builder {
public:
virtual ~Builder() = default;
virtual void addRice() = 0;
virtual void addDish() = 0;
virtual void addDrinks() = 0;
virtual std::shared_ptr<setMenu> getMeal() = 0;
// 打包
static void makePack(Builder *const style) { // virtual 机制
style->addRice();
style->addDish();
style->addDrinks();
}
} ;
打包时机(以套餐 A 为例)
// 制造套餐 A
class Builder_A : public Builder {
public:
// ...
std::shared_ptr<setMenu> getMeal() {
makePack(this);
return ptr;
}
// ...
} ;
以上 makePack 函数是 static 函数的原因是,不同套餐只是调用一种公有方法(类似 Java 中的 Interface),不需要实际对象。
总结:
以上分析,主要分为两个部分:
setMenu 产品部分,在本例中是套餐,是最终要制造的对象。
制造者部分 Builder 以及其派生类 Build_#,负责以抽象工厂的模式产生各种成分,并组合在一起。PS: 和抽象工厂之间的区别:抽象工厂得到的是单一成分,但是建造者模式得到的是多种成分组合在一起的对象。
最终得到这样一种架构:
namespace Restaurant {
// ---------------------- 产品部分 -------------------------
class setMenu {};
// ---------------------- 建造者部分 -----------------------
class Builder {} ;
// 制造套餐 A
class Builder_A : public Builder {} ;
// 制造套餐 B
class Builder_B : public Builder {} ;
// 制造套餐 C
class Builder_C : public Builder {}
}
以上分析得到的代码如下:
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <assert.h>
#include <map>
namespace Restaurant {
// 产品类, 此景中为 套餐(set menu)
class setMenu {
private:
std::vector<std::string> mealOrder; // 点的菜名
static std::map<const std::string, double> priceList;
public:
void addMeal(const std::string name) {
// assert(std::find(mealOrder.begin(), mealOrder.end(), const_cast<std::string>(name)) not_eq mealOrder.end());
mealOrder.emplace_back(name);
}
void payBill() {
auto comsumption = 0.00;
for(const auto &it : mealOrder) {
comsumption += priceList[it];
std::cout << priceList[it] << " RMB ------ " << it << "\n";
}
std::cout << "\n本次套餐消费总共 " << comsumption << " RMB\n\n";
mealOrder.clear();
mealOrder.shrink_to_fit(); // 消费之后清空本类型套餐积累的数额
}
};
std::map<const std::string, double> setMenu::priceList = {
// 主食价格
{ "rice", 3.00 },
{ "dongBei rice", 5.00 },
{ "Thai rice", 7.00 },
// 热菜价格
{ "drumSticks", 4.00 },
{ "pizza", 20.00 },
{ "beef", 25.00 },
{ "seaWeed", 6.00},
{ "salted Duck", 100.00 },
// 饮料价格
{ "soya-bean milk", 2.00 },
{ "orange juice", 3.00 },
{ "coconut milk", 5.00}
};
// --------------------------------------------------------------------------
// 建造者模型, 提供接口, 情景含义————制作套餐
class Builder {
public:
virtual ~Builder() = default;
virtual void addRice() = 0;
virtual void addDish() = 0;
virtual void addDrinks() = 0;
virtual std::shared_ptr<setMenu> getMeal() = 0;
// 打包
static void makePack(Builder *const style) { // virtual 机制
style->addRice();
style->addDish();
style->addDrinks();
}
} ;
// 制造套餐 A
class Builder_A : public Builder {
public:
void addRice() override {
ptr->addMeal("rice");
}
void addDish() override {
ptr->addMeal("pizza");
ptr->addMeal("drumSticks");
}
void addDrinks() override {
ptr->addMeal("orange juice");
}
std::shared_ptr<setMenu> getMeal() {
makePack(this);
return ptr;
}
private:
std::shared_ptr<setMenu> ptr = std::make_shared<setMenu>();
} ;
// 制造套餐 B
class Builder_B : public Builder {
public:
void addRice() override {
ptr->addMeal("dongBei rice");
}
void addDish() override {
ptr->addMeal("salted Duck");
}
void addDrinks() override {
ptr->addMeal("soya-bean milk");
}
std::shared_ptr<setMenu> getMeal() {
makePack(this);
return ptr;
}
private:
std::shared_ptr<setMenu> ptr = std::make_shared<setMenu>();
} ;
// // 制造套餐 C
class Builder_C : public Builder {
public:
void addRice() override {
ptr->addMeal("Thai rice");
}
void addDish() override {
ptr->addMeal("pizza");
ptr->addMeal("beef");
}
void addDrinks() override {
ptr->addMeal("coconut milk");
}
std::shared_ptr<setMenu> getMeal() {
makePack(this);
return ptr;
}
private:
std::shared_ptr<setMenu> ptr = std::make_shared<setMenu>();
} ;
}
int main() {
using namespace Restaurant;
std::cout << "\n------------- 小芳点套餐 A -------------\n\n";
std::shared_ptr<Builder> styleA = std::make_shared<Builder_A>();
std::shared_ptr<setMenu> XiaoFang = styleA->getMeal();
XiaoFang->payBill();
std::cout << "\n------------- 小明点套餐 B -------------\n\n";
std::shared_ptr<Builder> styleB = std::make_shared<Builder_B>();
std::shared_ptr<setMenu> XiaoMing = styleB->getMeal();
XiaoMing->payBill();
std::cout << "\n------------- 小杨点套餐 C -------------\n\n";
std::shared_ptr<Builder> styleC = std::make_shared<Builder_C>();
std::shared_ptr<setMenu> XiaoYang = styleC->getMeal();
XiaoYang->payBill();
std::cout << "\n------------- 小畅点套餐 C -------------\n\n";
std::shared_ptr<setMenu> XiaoChang = styleC->getMeal();
XiaoChang->payBill();
return 0;
}
运行结果:
------------- 小芳点套餐 A -------------3 RMB ------ rice
20 RMB ------ pizza
4 RMB ------ drumSticks
3 RMB ------ orange juice本次套餐消费总共 30 RMB
------------- 小明点套餐 B -------------5 RMB ------ dongBei rice
100 RMB ------ salted Duck
2 RMB ------ soya-bean milk本次套餐消费总共 107 RMB
------------- 小杨点套餐 C -------------7 RMB ------ Thai rice
20 RMB ------ pizza
25 RMB ------ beef
5 RMB ------ coconut milk本次套餐消费总共 57 RMB
------------- 小畅点套餐 C -------------7 RMB ------ Thai rice
20 RMB ------ pizza
25 RMB ------ beef
5 RMB ------ coconut milk本次套餐消费总共 57 RMB
以上就基本实现了建造者模式,其实就是抽象工厂模式的变种。
思考:打包部分(组合成分为产品)可以继续区分 ?
现实生活中,每一种套餐都可以有几种打包方式,例如简易包装,普通包装,豪华包装,不同包装方式的费用也是不一样的,如何区分?
解决思路 I :
// 建造者模型, 提供接口, 情景含义————制作套餐
class Builder {
public:
// ...
// 打包方式 1
static void makePack_I(Builder *const style) {}
// 打包方式 2
static void makePack_II(Builder *const style) {}
// 打包方式 3
static void makePack_III(Builder *const style) {}
} ;
如上,直接添加打包函数即可,言简意赅。Builder::getMeal 直接在内部选择不同包装方式即可。缺点:如果打包方式太多,会造成 Builder 类定义庞大,不方便管理,每添加一种打包方式,整个 Builder 类以及包含 Builder 声明头文件的文件都要重新编译。
解决思路 II :
多态,以继承的方式表征不同的打包方式,声明如下:
class Pack {
public:
virtual ~Pack() = default;
virtual void makePack(Builder *const style) = 0;
} ;
class Pack_I : public Pack {
public:
virtual void makePack(Builder *const style) {}
} ;
class Pack_II : public Pack {
public:
virtual void makePack(Builder *const style) {}
} ;
class Pack_III : public Pack {
public:
virtual void makePack(Builder *const style) {}
} ;
这样一来,针对不同的打包方式,可以收取不同的包装费,还可以实行优惠政策等。
具体打包过程修改如下:(以套餐 A 为例)
// 制造套餐 A
class Builder_A : public Builder {
public:
// ...
std::shared_ptr<setMenu> getMeal() {
static std::shared_ptr<Pack> Courier = std::make_shared<Pack_I>();
Courier->makePack(this);
return ptr;
}
} ;
以上某个套餐固定为某个包装方式即可,故 Pack 对象选择 static。
以上两种思路,都是可以的。我选择思路二,效果如下:
------------- 小芳点套餐 A -------------
3 RMB ------ rice
20 RMB ------ pizza
4 RMB ------ drumSticks
3 RMB ------ orange juice本次套餐消费总共 30 RMB
------------- 小明点套餐 B -------------5 RMB ------ dongBei rice
100 RMB ------ salted Duck
2 RMB ------ soya-bean milk
3 RMB ------ 2 号袋包装本次套餐消费总共 110 RMB
------------- 小杨点套餐 C -------------7 RMB ------ Thai rice
7 RMB ------ Thai rice
20 RMB ------ pizza
25 RMB ------ beef
5 RMB ------ coconut milk
5 RMB ------ 3 号袋包装本次套餐消费总共 69 RMB
------------- 小畅点套餐 C -------------7 RMB ------ Thai rice
7 RMB ------ Thai rice
20 RMB ------ pizza
25 RMB ------ beef
5 RMB ------ coconut milk
5 RMB ------ 3 号袋包装本次套餐消费总共 69 RMB
具体代码如下:
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <assert.h>
#include <map>
namespace Restaurant {
// 产品类, 此景中为 套餐(set menu)
class setMenu {
private:
std::vector<std::string> mealOrder; // 点的菜名
static std::map<const std::string, double> priceList;
public:
void addMeal(const std::string name) {
// assert(std::find(mealOrder.begin(), mealOrder.end(), const_cast<std::string>(name)) not_eq mealOrder.end());
mealOrder.emplace_back(name);
}
void payBill() {
auto comsumption = 0.00;
for(const auto &it : mealOrder) {
comsumption += priceList[it];
std::cout << priceList[it] << " RMB ------ " << it << "\n";
}
std::cout << "\n本次套餐消费总共 " << comsumption << " RMB\n\n";
mealOrder.clear();
mealOrder.shrink_to_fit(); // 消费之后清空本类型套餐积累的数额
}
};
std::map<const std::string, double> setMenu::priceList = {
// 主食价格
{ "rice", 3.00 },
{ "dongBei rice", 5.00 },
{ "Thai rice", 7.00 },
// 热菜价格
{ "drumSticks", 4.00 },
{ "pizza", 20.00 },
{ "beef", 25.00 },
{ "seaWeed", 6.00},
{ "salted Duck", 100.00 },
// 饮料价格
{ "soya-bean milk", 2.00 },
{ "orange juice", 3.00 },
{ "coconut milk", 5.00},
// 包装价格
{ "1 号袋包装", 2.00 },
{ "2 号袋包装", 3.00 },
{ "3 号袋包装", 5.00 }
};
// 建造者模型, 提供接口, 情景含义————制作套餐
class Builder {
public:
virtual ~Builder() = default;
virtual void addRice() = 0;
virtual void addDish() = 0;
virtual void addDrinks() = 0;
virtual void addPack() = 0;
virtual std::shared_ptr<setMenu> getMeal() = 0;
} ;
// --------------------------------------------------------------------------
class Pack {
public:
virtual ~Pack() = default;
virtual void makePack(Builder *const style) = 0;
} ;
class Pack_I : public Pack {
public:
virtual void makePack(Builder *const style) {
style->addRice();
style->addDish();
style->addDrinks();
}
} ;
class Pack_II : public Pack {
public:
virtual void makePack(Builder *const style) {
style->addRice();
style->addDish();
style->addDrinks();
style->addPack();
}
} ;
class Pack_III : public Pack {
public:
virtual void makePack(Builder *const style) {
style->addRice();
style->addRice();
style->addDish();
style->addDrinks();
style->addPack();
}
} ;
// ---------------------------------------------------------------------------
// 制造套餐 A
class Builder_A : public Builder {
public:
void addRice() override {
ptr->addMeal("rice");
}
void addDish() override {
ptr->addMeal("pizza");
ptr->addMeal("drumSticks");
}
void addDrinks() override {
ptr->addMeal("orange juice");
}
void addPack() override {
ptr->addMeal("1 号袋包装");
}
std::shared_ptr<setMenu> getMeal() {
static std::shared_ptr<Pack> Courier = std::make_shared<Pack_I>();
Courier->makePack(this);
return ptr;
}
private:
std::shared_ptr<setMenu> ptr = std::make_shared<setMenu>();
} ;
// 制造套餐 B
class Builder_B : public Builder {
public:
void addRice() override {
ptr->addMeal("dongBei rice");
}
void addDish() override {
ptr->addMeal("salted Duck");
}
void addDrinks() override {
ptr->addMeal("soya-bean milk");
}
void addPack() override {
ptr->addMeal("2 号袋包装");
}
std::shared_ptr<setMenu> getMeal() {
static std::shared_ptr<Pack> Courier = std::make_shared<Pack_II>();
Courier->makePack(this);
return ptr;
}
private:
std::shared_ptr<setMenu> ptr = std::make_shared<setMenu>();
} ;
// // 制造套餐 C
class Builder_C : public Builder {
public:
void addRice() override {
ptr->addMeal("Thai rice");
}
void addDish() override {
ptr->addMeal("pizza");
ptr->addMeal("beef");
}
void addDrinks() override {
ptr->addMeal("coconut milk");
}
void addPack() override {
ptr->addMeal("3 号袋包装");
}
std::shared_ptr<setMenu> getMeal() {
static std::shared_ptr<Pack> Courier = std::make_shared<Pack_III>();
Courier->makePack(this);
return ptr;
}
private:
std::shared_ptr<setMenu> ptr = std::make_shared<setMenu>();
} ;
}
int main() {
using namespace Restaurant;
std::cout << "\n------------- 小芳点套餐 A -------------\n\n";
std::shared_ptr<Builder> styleA = std::make_shared<Builder_A>();
std::shared_ptr<setMenu> XiaoFang = styleA->getMeal();
XiaoFang->payBill();
std::cout << "\n------------- 小明点套餐 B -------------\n\n";
std::shared_ptr<Builder> styleB = std::make_shared<Builder_B>();
std::shared_ptr<setMenu> XiaoMing = styleB->getMeal();
XiaoMing->payBill();
std::cout << "\n------------- 小杨点套餐 C -------------\n\n";
std::shared_ptr<Builder> styleC = std::make_shared<Builder_C>();
std::shared_ptr<setMenu> XiaoYang = styleC->getMeal();
XiaoYang->payBill();
std::cout << "\n------------- 小畅点套餐 C -------------\n\n";
std::shared_ptr<setMenu> XiaoChang = styleC->getMeal();
XiaoChang->payBill();
return 0;
}