创建型设计模式之build模式

最近在读《设计模式-可复用面向对象软件设计的基础》一书,在阅读的过程中我会结合书中的相关知识和实例以及在网络上的博客对相关的模式的理解进行总结,并在此基础上加入自己的一些理解,总结模式中需要注意的一些点,记录在此博客,以供大家交流分享,同时防止自己对内容遗忘,如有不正确指出,欢迎批评指正。

本节的主要内容是设计模式中的创建型模式之一:builder模式

要用好builder模式,必须对其机制了解透彻,将该模式用在合适的软件中才能显出它真正的威力,第五部分中实例将为您展示它的真正威力。

本文内容分为以下几个方面:

  • builder模式的意图
  • builder模式的适用性
  • builder模式的结构(通用UML类图)及详解
  • builder模式中的参与者
  • builder模式中各个成分之间的交互
  • builder模式比较好的一个实例
  • builder模式使用过程中注意的点
  • KFC套餐实例代码

builder模式的精髓都隐藏于builder模式的意图和实用性中,让你真正理解这两个方面,可以说你已经掌握了builder模式(以下意图和模式都摘自《设计模式》书中)

builder模式的意图

builder模式的意图是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

builder模式的适用性

  • 创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
  • 当构造过程必须允许被构造的对象有不同的表示时。

提前记录一下:装配的工作是由导向器(director)完成的,复杂对象的创建是由具体的建造器完成(concreteBuilder),这里不明白没问题,等你看完全文再过来看这里你就理解了。

当且仅当上面两个条件均满足的情况下我们要使用builder模式(完全理解这两条不一件重要的事情,但是不要着急,当你读完这篇文章的时候就会拨开它的庐山真面目了)

builder模式的结构(UML类图)及详解

builder模式的UML类图关系如下:

注意的问题:

  • 在builder中一般不声明纯虚函数,而是把它们定义为空方法,这使客户只重定义他们感兴趣的操作。
  • 在director中,注意我们不是把所有的buildPart都去执行一次,而是根据需要的客户的需求,进行定制的去buildPart部分构建,可以构建一次,也可以构建多次。
  • 一般将m_product声明为protected成员,因为这样既保证了封装性,又能使得concreteBuilder能够正常操作product;

builder模式种的参与者

从上面的类图中我们也可以看出,在builder模式中的参与者有以下四种,以及他们的职责分别是:(此处为个人理解,与书中内容可能不一致,如果有问题欢迎指正)

  • Director: 负责装配product的各个部件,使用Builder的类方法进行实现。它的作用是隔离了客户与product的具体生产过程;并负责控制product的生产过程。
  • Builder: 为创建一个product对象的各个部件指定抽象接口。一般情况下默认builderPart的操作默认为空。
  • concreteBuilder: 具体实现每一个部件的具体的复杂生产过程,如buildPartA()的具体实现,并提供一个检索产品的接口。
  • Product: 这个争议不大,即为被构造的复杂对象。此类对象会有不同的表示。

builder模式各个成分之间的交互方式

先上一张时序图:

从上面的时序图可以分析出,各个成分之间的交互方式如下:

  • 客户首先创建一个concreteBuilder对象,然后创建一个Director,
  • 创建结束之后,使用concreteBuilder修饰对象Director,
  • 然后调用Director的construct()方法,进行构造Product,
  • 最后客户通过concreteBuilder的getResult()方法取回生成的Product

builder模式中一个较好的实例

两个实例来自网络对两本书籍实例的纠正,改编

上面这篇文章深刻的分析了builder模式的本质以及诸多误区的分析,收益颇多,在此感谢博主分享。

在本例中,设备(Equipment)是一个复杂对象,由一个machine和一个或多个输入端口(InputPort)或者输出端口(outputPort)组成;其中输入或输出端口可能有不同的类型(ordinary和super)。现在要你设计一个生成不同型号的产品,要求产品可能包含一进一出(普通或super),一进两出(普通或super)。

  在设计中,我们首先定义一个LCDFactory对象充当director,一个设备生成器(EQPBuilder),相当于Builder。

​ 首先ordinary和super是port的内部实现方式不同,所以我们需要定义两个具体类,即 ordinaryEQPBuilder和superEQPBuilder。

​ 在EQPbuilder中,我们将定义四个函数,分别是:buildMachine(), addInputPort(), addOutputPort()和getEQP()。两个concrete类继承EQPBuilder。

​ 当我们需要获取不同数目的port的设备,这属于组装方面的范畴,所以我们将在LCDFactory中的createEQP中做。其UML类图如下:

builder模式在使用的过程中需要注意的问题

这里所说的注意的问题可能上面已经提到了,但是这里还是要着重强调一下,因为我们稍不注意,就可能将模式滥用,导致系统设计的失败。

  • 在builder模式中Builder一定不要定义纯虚函数成员函数,而是定义为空方法的虚函数,这样就可以使得客户可以只定义他们感兴趣的操作。
  • Director负责装配产品,concreteBuilder负责实现复杂产品部件的具体实现
  • 在Director中调用Builder的buildPart函数的时候,并不是每个函数都被调用,而是根据要生成的product对其选择性调用,可能调用零次,也可能调用多次。
  • Builder中的m_product声明为protected,即保证对象的封装性,又能让具体类方便的使用。(这是我在写代码的时候的解决方法,如果有更好的将m_product声明为private的解决方法欢迎交流2824759538@qq.com)

KFC套餐实例代码

这是本人使用KFC服务员生成套餐时的一种模拟,其实实际情况中并不需要使用builder模式,但是这里为了练手强行写成了builder模式,希望大家不要吐槽。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//KFCWaiter.hpp 注意此处water相当于Director的作用
#ifndef KFCWAITER_HPP_
#define KFCWAITER_HPP_
#include "MealBuilder.hpp"
class KFCWaiter {
public:
void setMealBuilder(std::shared_ptr<MealBuilder> t_builder);
void construct();
private:
std::shared_ptr<MealBuilder> m_builder;
};
#endif
1
2
3
4
5
6
7
8
9
//KFCWaiter.cpp
#include "KFCWaiter.hpp"
void KFCWaiter::setMealBuilder(std::shared_ptr<MealBuilder> t_builder) {
m_builder = t_builder;
}
void KFCWaiter::construct() {
m_builder->buildFood();
m_builder->buildDrink();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Meal.hpp Meal相当于Product
#ifndef MEAL_HPP
#define MEAL_HPP
#include <iostream>
#include <string>
class Meal {
public:
Meal(std::string t_food = "hanbao", std::string t_drink = "kele");
~Meal();
std::string getFood() const;
std::string getDrink() const;
void getMeal() const;
void buildFood(const std::string &t_food);
void buildDrink(const std::string &t_drink);
private:
std::string m_food;
std::string m_drink;
};
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Meal.cpp
#include "Meal.hpp"
Meal::Meal(std::string t_food, std::string t_drink)
: m_food(t_food), m_drink(t_drink) {}
Meal::~Meal() {}
std::string Meal::getFood() const { return m_food; }
std::string Meal::getDrink() const { return m_drink; }
void Meal::getMeal() const {
std::cout << "套餐为:" << std::endl
<< "食物:" << m_food << std::endl
<< "饮料:" << m_drink << std::endl;
}
void Meal::buildFood(const std::string &t_food) { m_food = t_food; }
void Meal::buildDrink(const std::string &t_drink) { m_drink = t_drink; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//MealBuilder.hpp
#ifndef BUILDER_HPP
#define BUILDER_HPP
#include "Meal.hpp"
#include <memory>
#include <string>
class MealBuilder {
public:
MealBuilder();
virtual void buildFood() = 0;
virtual void buildDrink() = 0;
std::shared_ptr<Meal> getResult();
virtual ~MealBuilder(){};
protected:
std::shared_ptr<Meal> m_meal;
};
#endif
1
2
3
4
//MealBuilder.cpp
#include "MealBuilder.hpp"
MealBuilder::MealBuilder() : m_meal(std::shared_ptr<Meal>(new Meal())) {}
std::shared_ptr<Meal> MealBuilder::getResult() { return m_meal; }
1
2
3
4
5
6
7
8
9
10
//MealBuilderA.hpp
#ifndef MEALBUILDERA_HPP_
#define MEALBUILDERA_HPP_
#include "MealBuilder.hpp"
class MealBuilderA : public MealBuilder {
public:
virtual void buildFood();
virtual void buildDrink();
};
#endif
1
2
3
4
5
//MealBuilderA.cpp
#include "MealBuilderA.hpp"
void MealBuilderA::buildFood() { m_meal->buildFood("套餐A食物"); }
void MealBuilderA::buildDrink() { m_meal->buildDrink("套餐A饮料"); }
1
2
3
4
5
6
7
8
9
10
//MealBuilderB.hpp
#ifndef MEALBUILDERB_HPP_
#define MEALBUILDERB_HPP_
#include "MealBuilder.hpp"
class MealBuilderB : public MealBuilder {
public:
virtual void buildFood();
virtual void buildDrink();
};
#endif
1
2
3
4
5
//MealBuilderB.cpp
#include "MealBuilderB.hpp"
void MealBuilderB::buildFood() { m_meal->buildFood("套餐B食物"); }
void MealBuilderB::buildDrink() { m_meal->buildDrink("套餐B饮料"); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//main.cpp
#include "KFCWaiter.hpp"
#include "MealBuilderA.hpp"
#include "MealBuilderB.hpp"
int main() {
std::shared_ptr<MealBuilder> Aptr =
std::shared_ptr<MealBuilder>(new MealBuilderA());
KFCWaiter k;
k.setMealBuilder(Aptr);
k.construct();
std::shared_ptr<Meal> current_meal = Aptr->getResult();
current_meal->getMeal();
}