在软件开发过程中有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成。例如,计算机是由 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的;游戏中的角色,也是有性别、个性、能力、脸型、体型、服装、发型等多个特性。
以上这些产品都是由多个部件构成的,各个部件可以灵活选择,但其创建步骤都大同小异。这类产品的创建无法用前面介绍的工厂模式描述,只有建造者模式可以很好地描述该类产品的创建。
一、定义与特点
建造者(Builder)模式的定义:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
该模式的主要优点如下:
- 封装性好,构建和表示分离。
- 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
- 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
其缺点如下:
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
- 产品越多,需要的具体建造者类也就越多。
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。
二、对建造者模式的理解
建造者模式的定义看着让人迷惑,什么叫构建和表示的分离?一个对象使用构造函数构造之后不就固定了,只能通过它的方法来改变它的属性,而且还要同样的构建过程搞出不同的表示,怎么可能呢?多写几个构造函数?
其实多写几个构造函数,根据不同参数设置对象不同的属性,也可以达到这样的效果,只是这样就非常麻烦了,每次要增加一种表示就要添加一个构造函数,将来构造函数会多得连自己都不记得了,这违背了开放-封闭的原则。
要不就只能多设计几个 set 函数,每次属性不一样了,就用 set 函数改变对象的属性。这样也可以达到效果。只是代码就会非常冗余了,每个要用到这个对象的地方,都要写上好几句语句,一旦对象有点什么变化,还得到处都改一遍,这样就很容易出错,而且这也违背了依赖倒转的原则。就像下面这样:
#include <iostream>
using namespace std;
// 手机产品类
class Phone
{
public:
Phone() {}
~Phone() {}
// 方式1:多写几个构造函数,根据不同参数设置对象不同的属性
Phone(string mainboard, string battery, string screen) {
m_mainboard = mainboard;
m_battery = battery;
m_screen = screen;
}
// 方式2:多设计几个set函数,每次属性不一样了,就用set函数改变对象的属性
void setMainboard(string mainboard) {m_mainboard = mainboard;}
void setBattery(string battery) {m_battery = battery;}
void setScreen(string screen) {m_screen = screen;}
void displayMainboard() {cout << m_mainboard << endl;}
void displayBattery() {cout << m_battery << endl;}
void displayScreen() {cout << m_screen << endl;}
private:
string m_mainboard;
string m_battery;
string m_screen;
};
int main()
{
// 方式1:多写几个构造函数,根据不同参数设置对象不同的属性
Phone* xiaomiPhone = new Phone("GaoTong", "Other", "Samsung");
// 显示其信息
xiaomiPhone->displayMainboard();
xiaomiPhone->displayBattery();
xiaomiPhone->displayScreen();
// 方式2:多设计几个set函数,每次属性不一样了,就用set函数改变对象的属性
Phone* applePhone = new Phone;
applePhone->setMainboard("A13");
applePhone->setBattery("apple");
applePhone->setScreen("Samsung");
// 显示其信息
applePhone->displayMainboard();
applePhone->displayBattery();
applePhone->displayScreen();
// 销毁指针
delete xiaomiPhone;
xiaomiPhone = NULL;
getchar();
}
输出如下:
GaoTong
Other
Samsung
A13
apple
Samsung
不能加很多构造函数,也不能直接用一堆 set 函数,然后发现,有些对象的构建是固定的几个步骤的,就像一条流水线一样,任何的产品都是通过每一个固定的步骤拼凑出来的。例如说一部手机,先放主板,再放屏幕,再放电池,再放外壳就能卖几千了,每次推出新产品,就换个更好的主板,换个大点的屏幕,再整个大容量电池,又能卖出个新价钱。就是说,这些步骤都没有变,变的只是每个部分的东西。
这就是大神的厉害之处了,透过现象看本质,基本有变的,有不变的,那敢情好,面向对象的一个重要指导思想就是,封装隔离变化的,留出不变的。于是他们就用一个 Builder 类把步骤中的每个部分封装起来,这个类的主要作用就是生产每个部件,再抽象一下提升高度,这样就依赖倒转了,这样每次只需要添加一个类,这个类还是这几个部分,只是内部的实现已经不一样了,这样就满足了开放-封闭的原则了。
但还是有一个问题,光有 Builder 类还不行,虽然产品的每个部分都有对应的函数,但是用起来的话,还是跟前面说的 set 函数一样,一用就要使用一大堆函数,也就是这变的东西是封装起来了,但这不变的东西还没留出来。这时,就添加一个 Director 类,这个类就是专门规定组装产品的步骤的,这样只要告诉 Director 使用哪个 Builder,就能生产出不同的产品,对于客户端来说,只看到用了 Director 的一个 construct 函数,甚是方便。
再反过来看建造者模式的定义,构建指的就是生产一个产品的步骤,表示就是每个产品部分的具体实现,通过 Director 封装步骤,通过 Builder 封装产品部分的实现,再把他两隔离开,就能隔离变的,留出不变的供客户端使用。
三、模式的结构
建造者模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成,现在我们来分析其基本结构和实现方法。主要角色如下。
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
其结构图如下图所示。
四、模式的实现
上图给出了建造者模式的主要结构,其相关类的代码如下:
(1) 产品角色:包含多个组成部件的复杂对象。
class Product {
public:
void setPartA(string partA) {m_partA = partA;}
void setPartB(string partB) {m_partB = partB;}
void setPartC(string partC) {m_partC = partC;}
void show() {
//显示产品的特性
cout << "m_partA: " << m_partA << endl;
cout << "m_partB: " << m_partB << endl;
cout << "m_partC: " << m_partC << endl;
}
private:
string m_partA;
string m_partB;
string m_partC;
};
(2) 抽象建造者:包含创建产品各个子部件的抽象方法。
class Builder {
protected:
Product *m_product; //创建产品对象
public:
Builder() {}
virtual void buildPartA() = 0;
virtual void buildPartB() = 0;
virtual void buildPartC() = 0;
//返回产品对象
Product *getResult() {return m_product;}
};
(3) 具体建造者:实现了抽象建造者接口。
class ConcreteBuilder : public Builder {
public:
ConcreteBuilder() {m_product = new Product();}
void buildPartA() {m_product->setPartA("PartA");}
void buildPartB() {m_product->setPartB("PartB");}
void buildPartC() {m_product->setPartC("PartC");}
};
(4) 指挥者:调用建造者中的方法完成复杂对象的创建。
class Director {
private:
Builder *m_builder;
public:
Director(Builder *builder) {m_builder = builder;}
//产品构建与组装方法
Product *construct() {
m_builder->buildPartA();
m_builder->buildPartB();
m_builder->buildPartC();
return m_builder->getResult();
}
};
(5) 客户类。
int main()
{
Builder *builder = new ConcreteBuilder();
Director *director = new Director(builder);
Product *product = director->construct();
product->show();
getchar();
}
输出如下:
m_partA: PartA
m_partB: PartB
m_partC: PartC
与工厂模式不同,建造者模式是在 Director 的控制下一步一步构造产品的。建造小人就是在 Builder 控制下一步步构造出来的。创建者模式可以能更精细的控制构建过程,从而能更精细的控制所得产品的内部结构。
五、应用实例
手机有很多,例如小米和苹果,虽然名称不一样,但是内部结构基本差不多,都是先放主板,再放电池,再放屏幕。这种场景就很适合使用建造者模式:
#include <iostream>
using namespace std;
#ifndef SAFE_DELETE
#define SAFE_DELETE(p) {if(p){delete(p); (p)=NULL;}}
#endif
// 手机产品类
class Phone
{
public:
Phone() {}
~Phone() {}
void setMainboard(string mainboard) {m_mainboard = mainboard;}
void setBattery(string battery) {m_battery = battery;}
void setScreen(string screen) {m_screen = screen;}
void displayMainboard() {cout << m_mainboard << endl;}
void displayBattery() {cout << m_battery << endl;}
void displayScreen() {cout << m_screen << endl;}
private:
string m_mainboard;
string m_battery;
string m_screen;
};
// 抽象建造者类
class Builder
{
public:
Builder() {}
~Builder() {}
protected:
Phone *m_phone;
public:
virtual void buildMainboard() = 0;
virtual void buildBattery() = 0;
virtual void buildScreen() = 0;
virtual Phone* getPhone() {return m_phone;}
};
// 小米建造者类
class XiaomiBuilder : public Builder
{
public:
XiaomiBuilder() {m_phone = new Phone;}
void buildMainboard() {m_phone->setMainboard("GaoTong");}
void buildBattery() {m_phone->setBattery("Other");}
void buildScreen() {m_phone->setScreen("Samsung");}
};
// 苹果建造者类
class AppleBuilder :public Builder
{
public:
AppleBuilder() {m_phone = new Phone;}
void buildMainboard() {m_phone->setMainboard("A13");}
void buildBattery() {m_phone->setBattery("apple");}
void buildScreen() {m_phone->setScreen("Samsung");}
};
// 指挥者类
class Director
{
public:
Director() {}
~Director() {}
Phone* Construct(Builder* builedr) {
builedr->buildScreen();
builedr->buildBattery();
builedr->buildMainboard();
return builedr->getPhone();
}
};
int main()
{
// 指挥者
Director *director = new Director;
// 构造小米手机产品,并获取小米手机对象
Builder *xiaomi = new XiaomiBuilder;
Phone* xiaomiPhone = director->Construct(xiaomi);
// 显示其信息
xiaomiPhone->displayMainboard();
xiaomiPhone->displayBattery();
xiaomiPhone->displayScreen();
// 构造苹果手机产品,并获取苹果手机对象
Builder* apple = new AppleBuilder;
Phone* applePhone = director->Construct(apple);
// 显示其信息
applePhone->displayMainboard();
applePhone->displayBattery();
applePhone->displayScreen();
// 销毁指针
SAFE_DELETE(director);
SAFE_DELETE(xiaomi);
SAFE_DELETE(xiaomiPhone);
SAFE_DELETE(apple);
SAFE_DELETE(applePhone);
getchar();
}
六、应用场景
建造者模式唯一区别于工厂模式的是针对复杂对象的创建。也就是说,如果创建简单对象,通常都是使用工厂模式进行创建,而如果创建复杂对象,就可以考虑使用建造者模式。
当需要创建的产品具备复杂创建过程时,可以抽取出共性创建过程,然后交由具体实现类自定义创建流程,使得同样的创建行为可以生产出不同的产品,分离了创建与表示,使创建产品的灵活性大大增加。
建造者模式主要适用于以下应用场景:
- 相同的方法,不同的执行顺序,产生不同的结果。
- 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
- 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
- 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。
七、建造者模式和工厂模式的区别
通过前面的学习,我们已经了解了建造者模式,那么它和工厂模式有什么区别呢?
- 建造者模式更加注重方法的调用顺序,工厂模式注重创建对象。
- 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样。
- 关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。
- 建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。
参考: