一天一个设计模式——生成器模式

692 阅读6分钟

参考:refactoringguru.cn/design-patt…

意图

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

问题

假设你需要创建一个复杂对象,例如Car对象。

通常,制造一辆普通汽车需要轮胎,车窗,底盘,发动机等设备。但是如果我们制造一辆豪车的话,除了上述设施外,可能还要带有GPS,防抱死系统,影音设备等设施。面对如此复杂对象的创建,应该怎么做呢?有以下两种常见做法:

  1. 扩展Car基类,为每种可能的对象都创建一个子类。缺点是将导致子类的数量庞大。 任何新增的参数都会让这个层次结构更加复杂。
  2. Car基类中创建一个包括所有可能参数的超级构造函数,用它来控制Car对象。缺点是绝大部分的参数可能用不到,且将导致构造函数的调用很复杂。

解决方案

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

该模式将对象构造过程划分为一组步骤。每次创建对象时,都需要通过生成器对象执行一系列的步骤。具体调用哪些步骤,取决于你要创建的是何种对象。如创建一辆普通的车,可能只需要调用安装轮胎,车窗,底盘等步骤即可。

**当需要创建不同形式的产品时,有些构造步骤可能需要不同的实现。**比如要创建坦克和小汽车两种不同形式的产品,那么对于安装轮胎这个步骤肯定是需要不同的实现。因为坦克安装的是履带,小汽车安装的是轮胎。这种情况下,就需要多个不同的生成器对象,采用不同的方式实现一组相同的创建步骤。然后在创建过程中,使用这些生成器来生成不同类型的对象。

还可以进一步将用于创建产品的一系列生成器步骤调用抽取成为单独的主管类。 **主管类可定义创建步骤的执行顺序, 而生成器则提供这些步骤的实现。**对于客户端来说, 主管类完全隐藏了产品构造细节。 客户端只需要将一个生成器与主管类关联, 然后使用主管类来构造产品, 就能从生成器处获得构造结果了。

利用生成器模式,对问题提出解决方案,且测试了如何复用相同的对象构造代码来生成不同类型的产品,类图如下:

生成器模式结构示例

生成器模式结构

生成器设计模式结构

  1. Builder:生成器接口,声明在所有类型生成器中通用的产品构造步骤。
  2. Concrete Builders:具体生成器,提供构造过程的不同实现。具体生成器可以构造不遵循通用接口的产品,如问题中的CarManual,两者并没有遵循同一接口,但是有关联(构造步骤相似)。**因此,具体生成器一般需要自行提供获取产品的方法。**但是, 如果所有产品都遵循同一接口, 那么可以安全地在Builder接口中添加获取产品的方法。
  3. Products:最终生成的产品对象。
  4. Director:主管类,定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置。在所有产品都遵循相同接口的情况下, 产品可以直接通过主管类获取。 否则, 客户端应当通过生成器获取产品。
  5. 客户端:将某个生成器对象与主管类关联。一般情况下, 只需通过主管类构造函数的参数进行一次性关联即可。

对问题的代码实现

#include<iostream>
#include<string>
using namespace std;

// 只有当产品较为复杂且需要详细配置时,使用生成器模式才有意义。下面的两个
// 产品尽管没有同样的接口,但却相互关联。
class Car {
public:
	int seats;
	string engine;
	bool GPS;
	bool trip_computer;
public:
	void show() {
		cout << "seats:" << this->seats << endl;
		cout << "engine:" << this->engine << endl;
		cout << "GPS:" << this->GPS << endl;
		cout << "trip_computer:" << this->trip_computer << endl;
	}
};

class Manual {
public:
	int seats;
	string engine;
	bool GPS;
	bool trip_computer;
public:
	void describe() {
		cout << "本车有" << this->seats << "个座位" << endl;
		if (!engine.empty())
			cout << "引擎型号是:" << this->engine << endl;
		else
			cout << "没有安装引擎" << endl;

		if (GPS)
			cout << "本车搭载了GPS" << endl;
		else
			cout<< "本车未搭载GPS" << endl;

		if (trip_computer)
			cout << "本车搭载了旅游电脑" << endl;
		else
			cout << "本车未搭载旅游电脑" << endl;	
	}
};

// 生成器接口声明了创建产品对象不同部件的方法。
class Builder {
public:
	~Builder()
	{

	}
	virtual void reset() = 0;
	virtual void set_seats(int num) = 0;
	virtual void set_engine(string engine) = 0;
	virtual void set_tripComputer(bool ans) = 0;
	virtual void set_GPS(bool ans) = 0;
};

// 具体生成器类将遵循生成器接口并提供生成步骤的具体实现。你的程序中可能会
// 有多个以不同方式实现的生成器变体。
class CarBuilder : public Builder {
private:
	// 一个新的生成器实例必须包含一个在后续组装过程中使用的空产品对象。
	Car* car;
public:
	CarBuilder() {
		this->reset();
	}
	// reset(重置)方法可清除正在生成的对象。
	virtual void reset() {
		this->car = new Car();
	}
	// 所有生成步骤都会与同一个产品实例进行交互。
	virtual void set_seats(int num) {
		this->car->seats = num;
	}
	virtual void set_engine(string engine) {
		this->car->engine = engine;
	}
	virtual void set_tripComputer(bool ans) {
		this->car->trip_computer = ans;
	}
	virtual void set_GPS(bool ans) {
		this->car->GPS = ans;
	}

	// 具体生成器需要自行提供获取结果的方法。这是因为不同类型的生成器可能
	// 会创建不遵循相同接口的、完全不同的产品。所以也就无法在生成器接口中
	// 声明这些方法(至少在静态类型的编程语言中是这样的)。
	//
	// 通常在生成器实例将结果返回给客户端后,它们应该做好生成另一个产品的
	// 准备。因此生成器实例通常会在 `getProduct(获取产品)`方法主体末尾
	// 调用重置方法。但是该行为并不是必需的,你也可让生成器等待客户端明确
	// 调用重置方法后再去处理之前的结果。
	Car* get_product() {
		Car* result = this->car;
		this->reset();
		return result;
	}
};

class ManaualBuilder : public Builder {
private:
	Manual* man;
public:
	~ManaualBuilder()
	{

	}
	ManaualBuilder() {
		this->reset();
	}
	virtual void reset() {
		this->man = new Manual();
	}
	virtual void set_seats(int num) {
		this->man->seats = num;
	}
	virtual void set_engine(string engine) {
		this->man->engine = engine;
	}
	virtual void set_tripComputer(bool ans) {
		this->man->trip_computer = ans;
	}
	virtual void set_GPS(bool ans) {
		this->man->GPS = ans;
	}

	Manual* get_product() {
		Manual* result = this->man;
		this->reset();
		return result;
	}
};

// 主管只负责按照特定顺序执行生成步骤。其在根据特定步骤或配置来生成产品时
// 会很有帮助。由于客户端可以直接控制生成器,所以严格意义上来说,主管类并
// 不是必需的。
class Director {
private:
	Builder* builder;
public:
	// 主管可同由客户端代码传递给自身的任何生成器实例进行交互。客户端可通
	// 过这种方式改变最新组装完毕的产品的最终类型。
	void set_builder(Builder* builder) {
		this->builder = builder;
	}
	// 主管可使用同样的生成步骤创建多个产品变体。
	void construct_sports_car() {
		builder->reset();
		builder->set_seats(2);
		builder->set_engine("HEXO1.0");
		builder->set_GPS(true);
		builder->set_tripComputer(true);
	}

	void construct_SUV() {
		builder->reset();
		builder->set_seats(4);
		builder->set_engine("HEXO2.0");
		builder->set_GPS(true);
	}
};

// 客户端代码会创建生成器对象并将其传递给主管,然后执行构造过程。最终结果
// 将需要从生成器对象中获取。
void ClientCode(Director* director) {
	CarBuilder *car_builder = new CarBuilder();
	director->set_builder(car_builder);
	cout << "创建一辆跑车" << endl;
	director->construct_sports_car();
	// 最终产品通常需要从生成器对象中获取,因为主管不知晓具体生成器和
	// 产品的存在,也不会对其产生依赖。
	Car* p_car = car_builder->get_product();
	p_car->show();
	delete p_car;

	cout << endl;

	ManaualBuilder *man_builder = new ManaualBuilder();
	director->set_builder(man_builder);
	cout << "创建一本跑车说明书" << endl;
	director->construct_sports_car();
	Manual *p_man = man_builder->get_product();
	p_man->describe();
	delete p_man;

	delete car_builder;
}

int main() {
	Director* director = new Director();
	ClientCode(director);
	delete director;
	return 0;
}

本模式的优缺点

  1. 优点
    • 你可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。
    • 生成不同形式的产品时, 你可以复用相同的制造代码。
    • 符合单一职责原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来。
  2. 缺点
    • 由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。