C++ 学习笔记(22) Builder Pattern

版权声明:如若转载,注明出处即可 https://blog.csdn.net/nishisiyuetian/article/details/82765562

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;
}

猜你喜欢

转载自blog.csdn.net/nishisiyuetian/article/details/82765562