建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。

例如,计算机是由 OPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。

生活中这样的例子很多,如游戏中的不同角色,其性别、个性、能力、脸型、体型、服装、发型等特性都有所差异;还有汽车中的方向盘、发动机、车架、轮胎等部件也多种多样;每封电子邮件的发件人、收件人、主题、内容、附件等内容也各不相同。

以上所有这些产品都是由多个部件构成的,各个部件可以灵活选择,但其创建步骤都大同小异。这类产品的创建无法用前面介绍的工厂模式描述,只有建造者模式可以很好地描述该类产品的创建。

2.1模式动机

无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有多个组成部分,如汽车,它包括车轮、方向盘、发送机等各种部件。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车,可以通过建造者模式对其进行设计与描述,建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

在软件开发中,也存在大量类似汽车一样的复杂对象,它们拥有一系列成员属性,这些成员属性中有些是引用类型的成员对象。而且在这些复杂对象中,还可能存在一些限制条件,如某些属性没有赋值则复杂对象不能作为一个完整的产品使用;有些属性的赋值必须按照某个顺序,一个属性没有赋值之前,另一个属性可能无法赋值等。

复杂对象相当于一辆有待建造的汽车,而对象的属性相当于汽车的部件,建造产品的过程就相当于组合部件的过程。由于组合部件的过程很复杂,因此,这些部件的组合过程往往被“外部化”到一个称作建造者的对象里,建造者返还给客户端的是一个已经建造完毕的完整产品对象,而用户无须关心该对象所包含的属性以及它们的组装方式,这就是建造者模式的模式动机。

2.2模式的定义与特点

造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式。

该模式的主要优点如下:

1.各个具体的建造者相互独立,有利于系统的扩展。
2.客户端不必知道产品内部组成的细节,便于控制细节风险。

其缺点如下:

1.产品的组成部分必须相同,这限制了其使用范围。
2.如果产品的内部变化复杂,该模式会增加很多的建造者类。

建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。

2.3模式的结构与实现

建造者(Builder)模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成,现在我们来分析其基本结构和实现方法。

2.3.1模式的结构

建造者(Builder)模式的主要角色如下。

 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个滅部件。
 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

其结构图如图 1 所示。
《设计模式》第二部分 创建型设计模式 第4章 建造者模式(A:C++实现)_建造者模式

图1 建造者模式的结构图

简单理解就是Builder中定义了创建Product各个部分的接口。ConcreteBuilder中具体实现了创建Product中的各个部分的接口,就是具体的建造者。Director是根据用户的需求构建Product的(具体怎么构建,怎么把Product中的各个部件构建起来。)该模式主要用于创建一些复杂的对象,这些兑现内部构建的建造顺序通常是稳定的,但对象内部的构建通常面临着复杂的变化。

2.3.2模式的实现(C++实现)

那么建造者(Builder)模式怎么实现呢?比如可以有不同得ConcreteBuilder类实现不同得创建方式。这样从应用上模板方法模式(后面的章节会讲)和建造者模式实现的功能是很相似的。但是建造者模式的侧重点是在于Director的不同,可以有不同的呈现方式,而模板方法模式的侧重点是算法中每一步实现的不同。另外,它们的类之间的关系是不同的。模板方法是通过继承的方式来实现的,而建造者模式是通过组合的方式实现的。模板方法模式主要用于执行不同的算法,建造者模式主要用于构建对象。不过者两种模式其实都可以实现很多相似的功能。这也没什么,本来同样的功能的实现方式也是多种多样的。主要看需求。另外在实际写程序的时候也不必拘泥于某种设计模式,只要遵守相关的原则,使整个程序高效、稳定、易扩展、易维护就行。两者使用的场景也有区别。

代码如下所示:

#include <iostream>
#include <vector>

using namespace std;
// 产品类
class Product
{
public:
	void Add(string str)
	{
		m_vec.push_back(str);
	}
	void Show()
	{
		for (auto it = m_vec.cbegin(); it != m_vec.cend(); it++)
		{
			cout << *it << endl;
		}
	}
private:
	vector<string> m_vec;
};

// 建造者类
class Builder
{
public:
	virtual void BuildPartA() = 0;
	virtual void BuildPartB() = 0;
	virtual void BuildPartC() = 0;
	virtual Product getResult() = 0;
};

class ConcreteBuilder_0 : public Builder
{
public:
	ConcreteBuilder_0() : m_p(nullptr)
	{
		m_p = new Product();
	}
	virtual ~ConcreteBuilder_0()
	{
		if (nullptr == m_p)
			delete m_p;
	}
	virtual void BuildPartA()
	{
		string str = "Builder_0 BuildPartA";
		m_p->Add(str);
	}
	virtual void BuildPartB()
	{
		string str = "Builder_0 BuildPartB";
		m_p->Add(str);
	}
	virtual void BuildPartC()
	{
		string str = "Builder_0 BuildPartC";
		m_p->Add(str);
	}
	Product getResult()
	{
		return *m_p;
	}
private:
	Product * m_p;
};

class ConcreteBuilder_1 : public Builder
{
public:
	ConcreteBuilder_1() : m_p(nullptr)
	{
		m_p = new Product();
	}
	virtual ~ConcreteBuilder_1()
	{
		if (nullptr == m_p)
			delete m_p;
	}
	virtual void BuildPartA()
	{
		string str = "Builder_1 BuildPartA";
		m_p->Add(str);
	}
	virtual void BuildPartB()
	{
		string str = "Builder_1 BuildPartB";
		m_p->Add(str);
	}
	virtual void BuildPartC()
	{
		string str = "Builder_1 BuildPartC";
		m_p->Add(str);
	}
	Product getResult()
	{
		return *m_p;
	}
private:
	Product * m_p;
};
// 指挥者类
class Director
{
public:
	// 具体怎么实现根据需求
	void build(Builder *p)
	{
		p->BuildPartA();
		p->BuildPartC();
		p->BuildPartB();
	}
};

int main(int argc, char *argv[])
{
    //动态关联, 动态编联
	// 建造者模式
	Builder *builder= new ConcreteBuilder_0();
	Director * director = new Director();
	director->build(builder);
	builder->getResult().Show();

	builder = new ConcreteBuilder_1();
	director->build(builder);
	builder->getResult().Show();

    if (director != NULL)
    {
        delete director;
        director = NULL;
    }

    if (builder != NULL)
    {
        delete builder;
        builder= NULL;
    }
    return 0;
}

2.4模式的应用场景

在以下情况下可以使用建造者模式:

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

在很多游戏软件中,地图包括天空、地面、背景等组成部分,人物角色包括人体、服装、装备等组成部分,可以使用建造者模式对其进行设计,通过不同的具体建造者创建不同类型的地图或人物。

2.5模式的扩展

建造者模式的简化:

 省略抽象建造者角色:如果系统中只需要一个具体建造者的话,可以省略掉抽象建造者。
 省略指挥者角色:在具体建造者只有一个的情况下,如果抽象建造者角色已经被省略掉,那么还可以省略指挥者角色,让Builder角色扮演指挥者与建造者双重角色。

建造者模式与抽象工厂模式的比较:

 与抽象工厂模式相比, 建造者模式返回一个组装好的完整产品 ,而 抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。
 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象。
 如果将抽象工厂模式看成汽车配件生产工厂 ,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

2.6总结

一个复杂对象是由多个部件组成的,建造者模式是把复杂对象的创建和部件的创建分别开来,分别用Builder类和Director类来表示。用Director构建最后的复杂对象,而在上面Builder接口中封装的是如何创建一个个部件(复杂对象是由这些部件组成的),也就是说,Director负责如何将部件最后组装成产品。这样建造者模式就让设计和实现解耦了。

刚开始接触建造者模式的时候,最容易把建造者和抽象工厂模式混淆了。由于而这都属于创建型的设计模式,所以二者之间是有公共点的,但是建造者模式注重于对象组合,即不同的小对象组成一个整体的复杂大对象,而抽象工厂模式针对于接口编程,只是对外提供创建对象的工厂接口,不负责对象之后的处理。

建造者模式,是一个比较复杂,不容易权衡的设计模式。大家应该更多的阅读开源代码,理解他人是如何使用该模式的。从实际的应用中学习设计模式。