C++设计模式---建造者/构建器/构建者/生成器模式

建造者模式通常用于创建比较复杂的对象。

文章目录

  • 建造者模式具体的应用情景1---怪物身体组件
  • 建造者模式具体的应用情景2---生成部门员工的工作日报
  • 建造者模式总结


建造者模式具体的应用情景1—怪物身体组件

假设你还是游戏程序员,游戏策划对你的表现非常满意,现在你们已经有了三种怪物类型:亡灵类、元素类、机械类。
现在策划又想给怪物进行细分,比如一个怪物有三个部位组成:头部、躯干、肢体。
三个部位的模型可以自由组合,这样你们公司只需要制作出若干种头部、躯干、肢体,就可以排列组合出很多种不同的怪物,从而节省经费。
你如何实现?

因为不同怪物的部位生成细节不相同,并不是简单的组合,需要调用自身的一些具体的修饰函数,比如亡灵类怪物和机械类怪物外观上,肯定就不相同。
所以可以将抽象怪物类实现选择头部、躯干、肢体的三种抽象函数,然后在子类中进行具体的实现。

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");
}

C++设计模式---建造者/构建器/构建者/生成器模式_第1张图片

上面的代码结构类似于模板方法模式,还不能称为建造者模式,我们接下来对代码进行修改,从而提高灵活性。
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;
}

C++设计模式---建造者/构建器/构建者/生成器模式_第2张图片

C++设计模式---建造者/构建器/构建者/生成器模式_第3张图片

综上,可以看到建造者模式一共包含四种角色:

  1. 抽象构建器(这里指MonsterBuilder),为创建一个产品的各个部件指定抽象接口,同时返回一个已经构建好的产品指针。
  2. 具体的构建器(这里指MonsterBuilder的子类),实现了抽象构建器的抽象接口。
  3. 产品(这里指不同的怪物类),被构建的复杂对象,它不同的部件由具体的构建器创建,并定义其装配过程。
  4. 指挥者(这里指MonsterDirector),通过指向抽象构建器的指针,来完成复杂对象具体的装配过程。

建造者模式具体的应用情景2—生成部门员工的工作日报

现在有各个部门的员工,他们工作日报有三部分:标题、内容主体、结尾。
标题:部门名称、日报生成日期等信息。
内容主体:具体的工作描述(可能有多条)。
结尾:员工姓名。

日报的导出可能有多种格式的文件,比如纯文本文件,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 += ""</span><span class="token punctuation">;</span>
            m_strResult <span class="token operator">+=</span> head<span class="token operator">-></span><span class="token function">getDepName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            m_strResult <span class="token operator">+=</span> <span class="token string">"";
            //剩下的省略
        }
        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;
    };
}

C++设计模式---建造者/构建器/构建者/生成器模式_第4张图片


建造者模式总结

建造者模式用于分步骤构建一个复杂的对象,其中构建步骤是一个稳定算法。

可以在一下场景下使用:

  1. 产品对象内部结构复杂,产品往往由多个零件组成。
  2. 需要创建的产品对象内部属性相互依赖,需要指定创建的次序。
  3. 创建复杂对象的步骤独立于该对象的组成部分。
  4. 将复杂对象的创建和使用分离,使相同的构建过程可以创建不同的产品。

指挥者类的作用:

  1. 通过部件以指定的顺序来构建整个产品,控制了构建过程
  2. 通过提供Construct接口隔离了客户端与具体构建过程所必须要调用的类的成员函数之间的关联。

建造者模式的优点:

  1. 产品构建和产品表现上的分离。构建算法可以被复用。
  2. 向客户隐藏了产品内部的实现细节。
  3. 产品的实现可以随时被替换。

建造者模式的缺点:

  1. 要求所创建的产品有比较多的共同点,创建步骤(组成部分)要大致相同。
  2. 该模式涉及到很多的类(很多Builder类),对于理解和学习有一定的门槛。

你可能感兴趣的:(设计模式,c++,设计模式,开发语言)