建造者模式通常用于创建比较复杂的对象。
假设你还是游戏程序员,游戏策划对你的表现非常满意,现在你们已经有了三种怪物类型:亡灵类、元素类、机械类。
现在策划又想给怪物进行细分,比如一个怪物有三个部位组成:头部、躯干、肢体。
三个部位的模型可以自由组合,这样你们公司只需要制作出若干种头部、躯干、肢体,就可以排列组合出很多种不同的怪物,从而节省经费。
你如何实现?
因为不同怪物的部位生成细节不相同,并不是简单的组合,需要调用自身的一些具体的修饰函数,比如亡灵类怪物和机械类怪物外观上,肯定就不相同。
所以可以将抽象怪物类实现选择头部、躯干、肢体的三种抽象函数,然后在子类中进行具体的实现。
namespace hjl_project1
{
class Monster
{
public:
virtual ~Monster() {}
//装配函数,
// id表示当前怪物的编号,比如123456789,
//这里前三位0~3代表躯干模型编号,4~6代表头部模型编号,7~9代表肢体模型编号
void Assemble(string id)
{
LoadTrunkModel(id.substr(0, 3));
LoadHeadModel(id.substr(4, 3));
LoadLimbsModel(id.substr(7, 3));
}
//载入躯干的函数
virtual void LoadTrunkModel(string strno) = 0;
//载入头部的函数
virtual void LoadHeadModel(string strno) = 0;
//载入肢体的函数
virtual void LoadLimbsModel(string strno) = 0;
};
class M_Undead : public Monster
{
public:
//载入躯干的函数
void LoadTrunkModel(string strno)
{
cout << "载入亡灵类怪物的躯干,要调用亡灵类怪物的其他成员函数,具体代码略过" << endl;
}
//载入头部的函数
virtual void LoadHeadModel(string strno)
{
cout << "载入亡灵类怪物的头部" << endl;
}
//载入肢体的函数
virtual void LoadLimbsModel(string strno)
{
cout << "载入亡灵类怪物的肢体" << endl;
}
};
class M_Element : public Monster
{
public:
//载入躯干的函数
void LoadTrunkModel(string strno)
{
cout << "载入元素类怪物的躯干,要调用元素类怪物的其他成员函数,具体代码略过" << endl;
}
//载入头部的函数
virtual void LoadHeadModel(string strno)
{
cout << "载入元素类怪物的头部" << endl;
}
//载入肢体的函数
virtual void LoadLimbsModel(string strno)
{
cout << "载入元素类怪物的肢体" << endl;
}
};
class M_Mechanic : public Monster
{
public:
//载入躯干的函数
void LoadTrunkModel(string strno)
{
cout << "载入机械类怪物的躯干,要调用机械类怪物的其他成员函数,具体代码略过" << endl;
}
//载入头部的函数
virtual void LoadHeadModel(string strno)
{
cout << "载入机械类怪物的头部" << endl;
}
//载入肢体的函数
virtual void LoadLimbsModel(string strno)
{
cout << "载入机械类怪物的肢体" << endl;
}
};
}
int main()
{
hjl_project1::Monster *pM1 = new hjl_project1::M_Element();
pM1->Assemble("123456789");
}
上面的代码结构类似于模板方法模式,还不能称为建造者模式,我们接下来对代码进行修改,从而提高灵活性。
Assemble、LoadTrunkModel、LoadHeadModel、LoadLimbsModel这四个函数称为构建过程的相关函数。这些函数放在怪物类中并不是很合适,因为我们希望怪物类中只保存怪物属性和动作相关的函数,而不是一些载入躯干、装配等函数。
我们可不可以引入与怪物类同层次的构建器类(类似于工厂类),将这四个函数放到构建器类中?
说干就干,首先你引入了一个构建器父类MonsterBuilder抽象类,这个类里面是载入配件的抽象函数,并有一个Monster指针指向对应的怪物类,后续的各种怪物类构建器类都可以继承并实现这些抽象函数,最后再引入指挥者类MonsterDirector,负责对于这些引入配件的装配。(这里为什么要引入指挥者类呢?主要是为了将构建部件和装配进行分离,从而降低构建器类的复杂度,当然不引入也行,直接在构建器类中进行装配)
namespace hjl_project2
{
class Monster
{
public:
virtual ~Monster() {}
};
class M_Undead : public Monster
{
public:
};
class M_Element : public Monster
{
public:
};
class M_Mechanic : public Monster
{
public:
};
//-----------------怪物构建器类-----------------
class MonsterBuilder
{
public:
virtual ~MonsterBuilder() {}
// void Assemble(string id)
// {
// LoadTrunkModel(id.substr(0, 3));
// LoadHeadModel(id.substr(4, 3));
// LoadLimbsModel(id.substr(7, 3));
// }
//当一个复杂的对象创建完成后,通过这个函数返回
Monster *GetMonster()
{
return m_pMonster;
}
//载入躯干的函数
virtual void LoadTrunkModel(string strno) = 0;
//载入头部的函数
virtual void LoadHeadModel(string strno) = 0;
//载入肢体的函数
virtual void LoadLimbsModel(string strno) = 0;
protected:
//通过指针,关联monster类
Monster *m_pMonster;
};
class M_UndeadBuilder : public MonsterBuilder
{
public:
M_UndeadBuilder()
{
m_pMonster = new M_Undead();
}
//载入躯干的函数
void LoadTrunkModel(string strno)
{
cout << "载入亡灵类怪物的躯干,要调用亡灵类怪物的其他成员函数,具体代码略过" << endl;
// m_pMonster->调用其他成员函数
}
//载入头部的函数
virtual void LoadHeadModel(string strno)
{
cout << "载入亡灵类怪物的头部" << endl;
}
//载入肢体的函数
virtual void LoadLimbsModel(string strno)
{
cout << "载入亡灵类怪物的肢体" << endl;
}
};
class M_ElementBuilder : public MonsterBuilder
{
public:
M_ElementBuilder()
{
m_pMonster = new M_Element();
}
//载入躯干的函数
void LoadTrunkModel(string strno)
{
cout << "载入元素类怪物的躯干,要调用元素类怪物的其他成员函数,具体代码略过" << endl;
}
//载入头部的函数
virtual void LoadHeadModel(string strno)
{
cout << "载入元素类怪物的头部" << endl;
}
//载入肢体的函数
virtual void LoadLimbsModel(string strno)
{
cout << "载入元素类怪物的肢体" << endl;
}
};
class M_MechanicBuilder : public MonsterBuilder
{
public:
M_MechanicBuilder()
{
m_pMonster = new M_Mechanic();
}
//载入躯干的函数
void LoadTrunkModel(string strno)
{
cout << "载入机械类怪物的躯干,要调用机械类怪物的其他成员函数,具体代码略过" << endl;
}
//载入头部的函数
virtual void LoadHeadModel(string strno)
{
cout << "载入机械类怪物的头部" << endl;
}
//载入肢体的函数
virtual void LoadLimbsModel(string strno)
{
cout << "载入机械类怪物的肢体" << endl;
}
};
//------------------指挥者类------------
class MonsterDirector
{
public:
MonsterDirector(MonsterBuilder *pBuilder)
: m_pMonsterBuilder(pBuilder)
{
}
//指定新的构建器
void SetBuilder(MonsterBuilder *pBuilder)
{
m_pMonsterBuilder = pBuilder;
}
//就是原来MonsterBuilder类中的Assemble函数
Monster *Construct(string id)
{
m_pMonsterBuilder->LoadTrunkModel(id.substr(0, 3));
m_pMonsterBuilder->LoadHeadModel(id.substr(4, 3));
m_pMonsterBuilder->LoadLimbsModel(id.substr(7, 3));
return m_pMonsterBuilder->GetMonster();
}
private:
MonsterBuilder *m_pMonsterBuilder;
};
}
int main()
{
hjl_project2::MonsterBuilder *pMonsterBuilder = new hjl_project2::M_UndeadBuilder();
hjl_project2::MonsterDirector *pDirector = new hjl_project2::MonsterDirector(pMonsterBuilder);
hjl_project2::Monster *pM1 = pDirector->Construct("123456789");
delete pMonsterBuilder, pDirector, pM1;
}
综上,可以看到建造者模式一共包含四种角色:
现在有各个部门的员工,他们工作日报有三部分:标题、内容主体、结尾。
标题:部门名称、日报生成日期等信息。
内容主体:具体的工作描述(可能有多条)。
结尾:员工姓名。
日报的导出可能有多种格式的文件,比如纯文本文件,XML文件等。
如果不用设计模式应该如何书写?
我们需要设计三个类,分别是标题、内容主体、结尾,然后再设计多个导入到不同格式文件的类。
namespace hjl_project3
{
//日报的标题部分
class DailyHeaderData
{
public:
DailyHeaderData(string strDepName, string strGenData)
: m_strDepName(strDepName), m_strGenData(strGenData)
{
}
private:
//部门名称
string m_strDepName;
//日报生成日期
string m_strGenData;
};
//日报中的内容主体部分
class DailyContentData
{
public:
DailyContentData(string strContent, double finishedTime)
: m_strContent(strContent), m_finishedTime(finishedTime)
{
}
//获取该项工作的内容
string getContent()
{
return m_strContent;
}
//获取完成时间
double getSpendTime()
{
return m_finishedTime;
}
private:
//工作内容描述
string m_strContent;
//该项工作完成花费的时间
double m_finishedTime;
};
//日报的结尾部分
class DailyFooterData
{
public:
DailyFooterData(string strUserName)
: m_strUserName(strUserName)
{
}
//获取员工姓名
string getUserName()
{
return m_strUserName;
}
private:
//员工姓名
string m_strUserName;
};
//将日报导出到纯文本文件
class ExportToTxtFile
{
public:
void doExport(DailyHeaderData *&head, vector<DailyContentData *> &contents, DailyFooterData *&foot)
{
//将head,contens,foot进行拼接成string,然后写到纯文本文件中
//代码省略
}
};
//将日报导出到XML文件
class ExportToXMLFile
{
public:
void doExport(DailyHeaderData *&head, vector<DailyContentData *> &contents, DailyFooterData *&foot)
{
//将head,contens,foot进行拼接成string,然后写到纯文本文件中
//代码省略
}
};
}
通过上面的代码,不难发现,导出文件有三个步骤是不变的,分别是:拼接标题,拼接内容主题,拼接结尾。我们可以考虑把这三个步骤抽象出来,形成一个通用的处理过程,只需要传入不同更多参数,就可以控制导出不同格式的文件。
即将构建不同格式数据的细节实现代码与具体的构建步骤分离,达到复用的目的。
如何采用设计模式书写?
namespace hjl_project4
{
//日报的标题部分
class DailyHeaderData
{
public:
DailyHeaderData(string strDepName, string strGenData)
: m_strDepName(strDepName), m_strGenData(strGenData)
{
}
//获取该项工作的内容
string getDepName()
{
return m_strDepName;
}
//获取完成时间
string getGenData()
{
return m_strGenData;
}
private:
//部门名称
string m_strDepName;
//日报生成日期
string m_strGenData;
};
//日报中的内容主体部分
class DailyContentData
{
public:
DailyContentData(string strContent, double finishedTime)
: m_strContent(strContent), m_finishedTime(finishedTime)
{
}
//获取该项工作的内容
string getContent()
{
return m_strContent;
}
//获取完成时间
double getSpendTime()
{
return m_finishedTime;
}
private:
//工作内容描述
string m_strContent;
//该项工作完成花费的时间
double m_finishedTime;
};
//日报的结尾部分
class DailyFooterData
{
public:
DailyFooterData(string strUserName)
: m_strUserName(strUserName)
{
}
//获取员工姓名
string getUserName()
{
return m_strUserName;
}
private:
//员工姓名
string m_strUserName;
};
//-------------------------构建器器类-------------------
class FileBuilder
{
public:
virtual ~FileBuilder() {}
virtual void buildHeader(DailyHeaderData *&head) = 0;
virtual void buildContents(vector<DailyContentData *> &contents) = 0;
virtual void buildFoot(DailyFooterData *&foot) = 0;
string GetResult()
{
return m_strResult;
}
protected:
string m_strResult;
};
//纯文本构建器类
class TxtFileBuilder : public FileBuilder
{
public:
virtual void buildHeader(DailyHeaderData *&head)
{
//具体的代码实现
m_strResult += head->getDepName();
//剩下的省略
}
virtual void buildContents(vector<DailyContentData *> &contents)
{
//拼接代码省略
}
virtual void buildFoot(DailyFooterData *&foot)
{
//拼接代码省略
}
};
//将日报导出到XML文件
class XMLFileBuilder : public FileBuilder
{
public:
virtual void buildHeader(DailyHeaderData *&head)
{
//具体的代码实现
m_strResult += "" ;
m_strResult += head->getDepName();
m_strResult += "";
//剩下的省略
}
virtual void buildContents(vector<DailyContentData *> &contents)
{
//拼接代码省略
}
virtual void buildFoot(DailyFooterData *&foot)
{
//拼接代码省略
}
};
//--------------------------实现一个文件指挥者类-------------------
class FileDirector
{
public:
FileDirector(FileBuilder *FileBuilder)
: m_FileBuilder(FileBuilder)
{
}
void SetFileBuilder(FileBuilder *FileBuilder)
{
m_FileBuilder=FileBuilder;
}
//组装文件
string Construct(DailyHeaderData *&head, vector<DailyContentData *> &contents, DailyFooterData *&foot)
{
//指挥者需要和构建器通过参数传递的方式交换数据,这里指挥者通过委托的方式把功能交给构建器完成
m_FileBuilder->buildHeader(head);
m_FileBuilder->buildContents(contents);
m_FileBuilder->buildFoot(foot);
return m_FileBuilder->GetResult();
}
private:
FileBuilder *m_FileBuilder;
};
}
建造者模式用于分步骤构建一个复杂的对象,其中构建步骤是一个稳定算法。
可以在一下场景下使用:
指挥者类的作用:
建造者模式的优点:
建造者模式的缺点: