生成器模式

生成器模式是一种创建型设计模式, 使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。

../../_images/builder.png

问题

假设有这样一个复杂对象, 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。 这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中;甚至还有更糟糕的情况, 那就是这些代码散落在客户端代码的多个位置。

例如, 我们来思考如何创建一个 房屋House对象。 建造一栋简单的房屋, 首先你需要建造四面墙和地板, 安装房门和一套窗户, 然后再建造一个屋顶。 但是如果你想要一栋更宽敞更明亮的房屋, 还要有院子和其他设施 (例如暖气、 排水和供电设备), 那又该怎么办呢?

最简单的方法是扩展 房屋基类, 然后创建一系列涵盖所有参数组合的子类。 但最终你将面对相当数量的子类。 任何新增的参数 (例如门廊类型) 都会让这个层次结构更加复杂。

另一种方法则无需生成子类。 你可以在 房屋基类中创建一个包括所有可能参数的超级构造函数, 并用它来控制房屋对象。 这种方法确实可以避免生成子类, 但它却会造成另外一个问题。

../../_images/house_problem.png

通常情况下, 绝大部分的参数都没有使用, 这使得对于构造函数的调用十分不简洁。 例如, 只有很少的房子有游泳池, 因此与游泳池相关的参数十之八九是毫无用处的。

解决方案

生成器模式建议将对象构造代码从产品类中抽取出来, 并将其放在一个名为生成器的独立对象中。

../../_images/house_solution.png

该模式会将对象构造过程划分为一组步骤, 比如 build­Walls创建墙壁和 build­Door创建房门创建房门等。 每次创建对象时, 你都需要通过生成器对象执行一系列步骤。 重点在于你无需调用所有步骤, 而只需调用创建特定对象配置所需的那些步骤即可。

当你需要创建不同形式的产品时, 其中的一些构造步骤可能需要不同的实现。 例如, 木屋的房门可能需要使用木头制造, 而城堡的房门则必须使用石头制造。

在这种情况下, 你可以创建多个不同的生成器, 用不同方式实现一组相同的创建步骤。 然后你就可以在创建过程中使用这些生成器 (例如按顺序调用多个构造步骤) 来生成不同类型的对象。

主管

你可以进一步将用于创建产品的一系列生成器步骤调用抽取成为单独的主管类。 主管类可定义创建步骤的执行顺序, 而生成器则提供这些步骤的实现。

严格来说, 你的程序中并不一定需要主管类。 客户端代码可直接以特定顺序调用创建步骤。不过, 主管类中非常适合放入各种例行构造流程, 以便在程序中反复使用。

此外, 对于客户端代码来说, 主管类完全隐藏了产品构造细节。 客户端只需要将一个生成器与主管类关联, 然后使用主管类来构造产品, 就能从生成器处获得构造结果了。

生成器模式结构

../../_images/builder_structure.png

  • 生成器 (Builder) 接口声明在所有类型生成器中通用的产品构造步骤。
  • 具体生成器 (Concrete Builders) 提供构造过程的不同实现。 具体生成器也可以构造不遵循通用接口的产品。
  • 产品 (Products) 是最终生成的对象。 由不同生成器构造的产品无需属于同一类层次结构或接口。
  • 主管 (Director) 类定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置。
  • 客户端 (Client) 必须将某个生成器对象与主管类关联。一般情况下, 你只需通过主管类构造函数的参数进行一次性关联即可。 此后主管类就能使用生成器对象完成后续所有的构造任务。 但在客户端将生成器对象传递给主管类制造方法时还有另一种方式。 在这种情况下, 你在使用主管类生产产品时每次都可以使用不同的生成器。

代码示例

//@ 电脑
class Computer
{
public:
	void setmCpu(string cpu) { strCpu_ = cpu; }
	void setmMainboard(string mainboard) { strMainboard_ = mainboard; }
	void setmRam(string ram) { strRam_ = ram; }
	void setVideoCard(string videoCard) { strVideoCard_ = videoCard; }

	string getCPU() const { return strCpu_; }
	string getMainboard() const { return strMainboard_; }
	string getRam() const { return strRam_; }
	string getVideoCard() const { return strVideoCard_; }

private:
	string strCpu_;	//@ CPU
	string strMainboard_; //@ 主板
	string strRam_;	//@ 内存
	string strVideoCard_; //@ 显卡
};

//@ 建造者接口,组装流程
class IBuilder
{
public:
	virtual void buildCpu() = 0;
	virtual void buildMainboard() = 0;
	virtual void buildRam() = 0;
	virtual void buildVideoCard() = 0;
	virtual std::shared_ptr<Computer> getResult() = 0;

	virtual ~IBuilder() = default;
};

//@ ThinkPad 系列
class ThinkPadBuilder : public IBuilder
{
public:
	ThinkPadBuilder() { pComputer_ = std::make_unique<Computer>(); }

	virtual void buildCpu() override { pComputer_->setmCpu("i5-6200U"); }
	virtual void buildMainboard() override { pComputer_->setmMainboard("Intel DH57DD"); }
	virtual void buildRam() override { pComputer_->setmRam("DDR4"); }
	virtual void buildVideoCard() override { pComputer_->setVideoCard("NVIDIA Geforce 920MX"); }

	std::shared_ptr<Computer> getResult() { return pComputer_; }

private:
	std::shared_ptr<Computer> pComputer_;
};

//@ Yoga 系列
class YogaBuilder : public IBuilder
{
public:
	YogaBuilder() { pComputer_ = std::make_unique<Computer>(); }

	virtual void buildCpu() override { pComputer_->setmCpu("i7-7500U"); }
	virtual void buildMainboard() override { pComputer_->setmMainboard("Intel DP55KG"); }
	virtual void buildRam() override { pComputer_->setmRam("DDR5"); }
	virtual void buildVideoCard() override { pComputer_->setVideoCard("NVIDIA GeForce 940MX"); }

	std::shared_ptr<Computer> getResult() { return pComputer_; }

private:
	std::shared_ptr<Computer> pComputer_;
};


//@ 构造指挥官
class Director
{
public:
	void create(std::shared_ptr<IBuilder>& builder)
	{
		builder->buildCpu();
		builder->buildMainboard();
		builder->buildRam();
		builder->buildVideoCard();
	}
};

int main()
{
	std::unique_ptr<Director> pDirecror(new Director());
	std::shared_ptr<IBuilder> pTPBuilder(new ThinkPadBuilder());
	std::shared_ptr<IBuilder> pYogaBuilder(new YogaBuilder());

	//@ 组装
	pDirecror->create(pTPBuilder);
	pDirecror->create(pYogaBuilder);

	//@ 获取组装结果
	std::shared_ptr<Computer> pThinkPadComputer = pTPBuilder->getResult();
	std::shared_ptr<Computer> pYogaComputer = pYogaBuilder->getResult();

	std::cout << "-----ThinkPad-----" << std::endl;
	std::cout << "CPU: " << pThinkPadComputer->getCPU() << std::endl;
	std::cout << "Mainboard: " << pThinkPadComputer->getMainboard() << std::endl;
	std::cout << "Ram: " << pThinkPadComputer->getRam() << std::endl;
	std::cout << "VideoCard: " << pThinkPadComputer->getVideoCard() << std::endl;

	std::cout << "-----Yoga-----" << std::endl;
	std::cout << "CPU: " << pYogaComputer->getCPU() << std::endl;
	std::cout << "Mainboard: " << pYogaComputer->getMainboard() << std::endl;
	std::cout << "Ram: " << pYogaComputer->getRam() << std::endl;
	std::cout << "VideoCard: " << pYogaComputer->getVideoCard() << std::endl;

	return 0;
}

生成器模式总结

实现方式

  • 清晰地定义通用步骤, 确保它们可以制造所有形式的产品。 否则你将无法进一步实施该模式。
  • 在基本生成器接口中声明这些步骤。
  • 为每个形式的产品创建具体生成器类, 并实现其构造步骤。
    • 不要忘记实现获取构造结果对象的方法。 你不能在生成器接口中声明该方法, 因为不同生成器构造的产品可能没有公共接口, 因此你就不知道该方法返回的对象类型。 但是, 如果所有产品都位于单一类层次中, 你就可以安全地在基本接口中添加获取生成对象的方法。
  • 考虑创建主管类。 它可以使用同一生成器对象来封装多种构造产品的方式。
  • 客户端代码会同时创建生成器和主管对象。 构造开始前, 客户端必须将生成器对象传递给主管对象。 通常情况下, 客户端只需调用主管类构造函数一次即可。 主管类使用生成器对象完成后续所有制造任务。 还有另一种方式, 那就是客户端可以将生成器对象直接传递给主管类的制造方法。
  • 只有在所有产品都遵循相同接口的情况下, 构造结果可以直接通过主管类获取。 否则,客户端应当通过生成器获取构造结果。

优点

  • 可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。
  • 生成不同形式的产品时, 你可以复用相同的制造代码。
  • 单一职责原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来。

缺点

  • 由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。

适用场景

  • 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
  • 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
  • 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入了主管类,将创建过程封装在主管类中,而不在建造者类和客户类中。
  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。