概述
建造者模式又称为生成器模式,它是一种较为复杂、使用频率也相对较较低的创建型模式。建造者模式为客户端返回的不是一个简单的产品,而是一个由多个部件组成的复杂产品。
建造者模式是较为复杂的创建型模式,它将客户端与包含多个组成部分(或部件)的复杂对象的创建过程分离,客户端无须知道复杂对象的内部组成部分与装配方式,只需要知道所需的建造者类型即可。建造者模式关注如何一步一步地创建一个复杂对象,不同的具体建造者定义了不同的创建过程,且具体建造者相互独立,增加新的建造者非常方便,无须修改已有代码,系统具有较好的扩展性。
建造者模式定义如下:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。
建造者模式一步一步地创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式结构如图所示:
从图中可以看出,在建造者模式结构图中包含以下4个角色:
- Builder(抽象建造者):它为创建一个产品 Product 对象的各个部件指定抽象接口,在该接口中一般声明两类方法:一类方法是 buildPartX(),用于创建复杂对象的各个部件;另一类方法是 getResult(),用于返回复杂对象。Builder既可以是抽象类,也可以是接口。
- ConcreteBuilder(具体建造者):它实现了 Builder 接口,实现各个部件的具体构造和装配方法,定义并明确其创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。
- Product(产品角色):它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义其装配过程。
- Director(指挥者):指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其 construct() 建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制),然后通过指挥者类的构造函数或者 Setter 方法将该对象传入指挥者类中。
在建造者模式中所说的复杂对象是指那些包含多个成员变量的对象,这些成员变量也称为部件或零件,例如,汽车包括方向
盘、发动机、轮胎等部件,电子邮件包括发件人、收件人、主题、内容、附件等部件。一个典型的复杂对象类代码示例如下:
class Product {
public:
string getPartA() const { return partA; }
void setPartA(const string& str) { partA = str; }
int getPartB() const { return partB; }
void setPartB(int num) { partB = num; }
map<int, string> getPartC() const { return partC; }
void setPartC(const map<int, string>& hash) { partC = hash; }
private:
string partA;
int partB;
map<int, string> partC;
};
在抽象建造者类中定义了产品的创建方法和返回方法,其典型代码如下:
class Builder {
public:
virtual void buildPartA() = 0;
virtual void buildPartB() = 0;
virtual void buildPartC() = 0;
Product* getResult() const { return product; }
protected:
Product* product;
};
在抽象类 Builder 中声明了一系列抽象的 buildPartX() 方法,用于创建复杂产品的各个部件,具体建造过程在 ConcreteBuilder 中实现,此外还提供了工厂方法 getResult(),用于返回一个建造好的完整产品。
在 ConcreteBuilder 中实现了 buildPartX() 方法,通过调用 Product 的 setPartX() 方法可以给产品对象的成员变量设值。不同的具体建造者在实现 buildPartX() 方法时将有所区别,例如 setPartX() 方法的参数可能不一样,在有些具体建造者类中某些 setPartX() 方法无须实现(提供一个空实现)。而这些对于客户端来说都无须关心,客户端只需知道具体建造者类型即可。
在建造者模式的结构中还引入了一个指挥者类 Director,该类主要有两个作用:一方面它隔离了客户与创建过程;另一方面它控制产品的创建过程,包括某个 buildPartX() 方法是否被调用,以及多个 buildPartX() 方法调用的先后次序等。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个
完整的产品对象。在实际生活中也存在类似指挥者一样的角色,例如一个客户去购买计算机,计算机销售人员相当于指挥者,只要客户确定计算机的类型,计算机销售人员可以通知计算机组装人员给客户组装一台计算机。指挥者类的代码示例如下:
class Director {
public:
Director(const Builder& build) : builder(build) {}
void setBuilder(const Builder& build) { builder = build; }
Product* construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
private:
Builder builder;
};
在指挥者类中可以注入一个抽象建造者类型的对象,其核心在于提供了一个建造方法 construct(),在该方法中调用了 builder 对象的构造部件的方法,最后返回一个产品对象。
对于客户端而言,只需关心具体的建造者即可。一般情况下,客户端类代码片段如下:
Builder* builder = new ConcreteBuilder();
Director director(builder);
Product* product = director.construct();
建造者模式与抽象工厂模式有点相似,但是建造者模式返回一个完整的复杂产品,而抽象工厂模式返回一系列相关的产品;在抽象工厂模式中,客户端通过选择具体工厂来生成所需对象,而在建造者模式中,客户端通过指定具体建造者类型并指导 Director 类如何去生成对象,侧重于一步一步地构造一个复杂对象,然后将结果返回。如果将抽象工厂模式看成
一个汽车配件生产厂,生成不同类型的汽车配件,那么建造者模式就是一个汽车组装厂,通过对配件进行组装返回一辆完整的汽车。
Director的演化
指挥者类 Director 在建造者模式中扮演非常重要的角色,简单的 Director 类用于指导具体建造者如何构建产品,它按一定次序调用 Builder 的 buildPartX() 方法,控制调用的先后次序,并向客户端返回一个完整的产品对象。
1. 省略Director
在有些情况下,为了简化系统结构,可以将 Director 和抽象建造者 Builder 进行合并,在 Builder 中提供逐步构建复杂产品对象的 construct() 方法。由于 Builder 类通常为抽象类,因此可以将 construct() 方法定义为静态(static) 方法。例如:
class Builder {
public:
virtual void buildA() = 0;
virtual void buildB() = 0;
virtual void buildC() = 0;
static Proudct* construct(const Builder& builder) {
builder.buildA();
builder.buildB();
builder.buildC();
return product;
}
protected:
static Product* product;
};
Product* Builder::product = new Product();
此外,还可以将 construct() 方法的参数去掉,直接在 construct() 方法中调用 buildPartX() 方法,例如:
class Builder {
public:
virtual void buildA() = 0;
virtual void buildB() = 0;
virtual void buildC() = 0;
Proudct* construct(const Builder& builder) {
this->buildA();
this->buildB();
this->buildC();
return product;
}
protected:
Product* product;
};
以上两种对 Director 类的省略方式都不影响系统的灵活性和可扩展性,同时还简化了系统结构,但加重了抽象建造者类的职责,如果 construct() 方法较为复杂,待构建产品的组成部分较多,建议还是将 construct() 方法单独封装在 Director 中,这样做更符合单一职责原则。
钩子方法
建造者模式除了逐步构建一个复杂产品对象外,还可以通过 Director 类来更加精细地控制产品的创建过程,例如增加一类称之为钩子方法(Hook Method)的特殊方法来控制是否调用某个buildPartX()方法。
钩子方法的返回类型通常为 Boolean 类型,方法名一般为 is×××()。钩子方法定义在抽象建造者类中。例如,可以在游戏角色的抽象建造者类 ActorBuilder 中定义一个方法 isBareheaded(),用于判断某个角色是否为 "光头(Bareheaded)",在 ActorBuilder 中为之提供一个默认实现,其返回值为 false,代码如下:
class ActorBuilder {
public:
virtual void buildType() = 0;
virtual void buildSex() = 0;
virtual void buildFace() = 0;
virtual void buildCostume() = 0;
virtual void buildHairstyle() = 0;
virtual bool isBareheaded() const { return false; }
Actor* createActor() const { return actor; }
protected:
Actor* actor;
};
如果某个角色无须构建头发部件,例如"恶魔(Devl)",则对应的具体建造者 DevilBuilder 将覆盖 isBareheaded() 方法,并将返回值改为 true,代码如下:
class DevilBuilder : public ActorBuilder {
public:
virtual void buildType() override;
virtual void buildSex() override;
virtual void buildFace() override;
virtual void buildCostume() override;
virtual void buildHairstyle() override;
virtual bool isBareheaded() const { return true; }
Actor* createActor() const { return actor; }
protected:
Actor* actor;
};
通过引入钩子方法,可以在 Director 中对复杂产品的构建进行精细控制,不仅指定 buildPartX() 方法的执行顺序,还可以控制是否需要执行某个 buildPartX() 方法。
总结
建造者模式的核心在于如何一步一步地构建一个包含多个组成部件的完整对象,使用相同的构建过程构建不同的产品。在软件开发中,如果需要创建复杂对象,并希望系统具备好的灵活性和可扩展性,可以考虑使用建造者模式。
优点
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 每一个具体建造者都相对独立,而与其他具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。由于指挥者类针对抽象建造者编程,增加新的具体建造者无须修改原有类库的代码,系统扩展方便,符合开闭原则。
- 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
缺点
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,例如很多组成部分都不相同,就不适合使用建造者模式,因此其使用范围受到一定的限制。
- 如果产品的内部结构复杂且多变,可能会需要定义很多具体建造者类来实现这种变化,这就导致系统变得很庞大,增加系统的理解难度和运行成本。
适用场景
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量。
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
- 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中。
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
标签:对象,创建,void,建造,virtual,模式,设计模式,方法 From: https://www.cnblogs.com/tuilk/p/16823762.html所有的代码见 Kohirus-Github