设计模式:原型模式的理解与应用

原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

介绍

意图:
    用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
 
特点:
  1. 性能优良:原型模式是在内存二进制流的复制,要比直接new一个对象性能好,特别是在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
  2. 逃避构造函数的约束:这既是优点也是缺点,直接在内存中复制,构造函数是不会执行的,因此减少了约束,需要在实际应用时进行权衡考虑。
何时使用: 
  1. 资源优化场景,类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
  2. 性能和安全要求的场景,通过new产生一个对象需要非常烦琐的数据准备或访问权限。
  3. 有的时候,创建对象时,构造函数的参数很多,而自己又不完全的知道每个参数的意义,就可以使用原型模式来创建一个新的对象,不必去理会创建的过程。
  4. 当我们的对象类型不是开始就能确定的,而这个类型是在运行期确定的话,那么我们通过这个类型的对象克隆出一个新的对象比较容易一些。 
  5. 当一个类的实例只能有几个不同状态组合中的一种时;建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
  6. 我自己感觉,和拷贝构造相比,隐藏了拷贝的细节,把拷贝构造进行了封装。

理解

 
C++ 类有两个特殊的构造函数,即无参构造函数、拷贝构造函数:
  1. 当类中没有定义任何的构造函数时,编译器会默认提供一个无参构造函数且函数体为空
  2. 当类中没有定义任何的拷贝构造函数时,编译器会默认提供一个拷贝构造函数,进行成员变量之间的拷贝;但是这个拷贝操作是 浅拷贝
浅拷贝(shallowCopy) :只是增加了一个指针指向已存在的内存地址
深拷贝(deepCopy) :是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存
 
      
 

实现

 
例如需要以下的一个功能:
 
  1. 定义人物技能属性有:战斗力、飞行距离、防御力
  2. 创建人物本体,该人物可以分身,即自我复制,但是最多只能复制三个
  3. 复制后的分身各属性为本体的 80%,同时每次分身都会导致本体降低 10% 的属性,但不会影响之前已有的分身(分身作为独立的个体),即使本体属性提升也不会影响分身
 
代码实现:
 
 
1、首先需要定义人物技能属性类
 

/**
* @brief 人物技能属性类
*/
class CSkillAttributes
{
private:
    int m_combatPower; // 战斗力
    int m_defensePower; // 防御力
    int m_fightDistance; // 分行距离

public:
    /**
     * @brief 构造函数
     * @param[in] combatPower     战斗力值
     * @param[in] defensePower    防御力值
     * @param[in] fightDistance   飞行距离
     * @return None
     */
    CSkillAttributes (int combatPower, int defensePower, int fightDistance):
                            m_combatPower(combatPower),
                            m_defensePower(defensePower),
                            m_fightDistance(fightDistance)
    {
    }

    /**
      * @brief     获取战斗力
      * @return    战斗力
      */
    int getCombatPower()
    {
        return m_combatPower;
    }

    /**
      * @brief     设置战斗力
      * @param[in] combatPower     战斗力值
      * @return None
      */
    void setCombatPower(int combatPower)
    {
        m_combatPower = combatPower;
    }

    /**
     * @brief     获取防御力
     * @return    防御力
     */
    int getDefensePower()
    {
        return m_defensePower;
    }

    /**
      * @brief     设置防御力
      * @param[in] defensePower 防御力值
      * @return None
      */
    void setDefensePower(int defensePower)
    {
        m_defensePower = defensePower;
    }

    /**
      * @brief     获取飞行距离
      * @return    飞行距离
      */
    int getFightDistance()
    {
        return m_fightDistance;
    }

    /**
      * @brief     设置飞行距离
      * @param[in] fightDistance 飞行距离
      * @return None
      */
    void setFightDistance(int fightDistance)
    {
        m_fightDistance = fightDistance;
    }
};

2、定义人物信息类,技能属性作为指针,在需要的时候分配内存,其中拷贝构造函数为私有,目前是防止越过克隆限制直接进行拷贝构造。

/**
* @brief 人物信息
*/
class CCharacter
{
private:
    bool isSelf;                // 是否为本体
    char m_name[50];            // 人物姓名
    int m_numLimit;             // 分身次数
    CSkillAttributes *pSkillAttributes; // 技能属性

public:
    /**
      * @brief 构造函数
      * @param[in] name 人物
      * @param[in] combatPower     战斗力值
      * @param[in] defensePower    防御力值
      * @param[in] fightDistance   飞行距离
      * @return None
      */
    CCharacter(const char *name, int combatPower, int defensePower, int fightDistance)
    {
        memset(m_name, 0, sizeof(m_name));
        memcpy(m_name, name, strlen(name));
        isSelf = true;// 标记本体
        printf("\e[%d;%dm%s[%s]\e[0m\n", 1, 31, "构造人物信息", m_name);
        m_numLimit = 0;
        pSkillAttributes = new CSkillAttributes(combatPower, defensePower, fightDistance);
    }

private:
    /**
      * @brief     拷贝构造函数
      * @param[in] src 类
      * @return None
      */
    CCharacter(CCharacter &src)
    {
        printf("\e[%d;%dm%s\e[0m\n", 1, 32, "复制人物信息");
        pSkillAttributes = new CSkillAttributes(
                            src.pSkillAttributes->getCombatPower() * 0.8,
                            src.pSkillAttributes->getDefensePower() * 0.8,
                            src.pSkillAttributes->getFightDistance() * 0.8);// 深度拷贝

        sprintf(m_name, "%s分身%d", src.m_name, src.m_numLimit);
        isSelf = false; // 标记分身

        /* 本身为原来的 90% 的属性 */
        src.pSkillAttributes->setCombatPower(src.pSkillAttributes->getCombatPower() * 0.9);
        src.pSkillAttributes->setDefensePower(src.pSkillAttributes->getDefensePower() * 0.9);
        src.pSkillAttributes->setFightDistance(src.pSkillAttributes->getFightDistance() * 0.9);
    }

public:

    /**
     * @brief     设置技能属性
     * @param[in] combatPower     战斗力值
     * @param[in] defensePower    防御力值
     * @param[in] fightDistance   飞行距离
     * @return None
     */
    void setSkillAttributes(int combatPower, int defensePower, int fightDistance)
    {
        pSkillAttributes->setCombatPower(combatPower);
        pSkillAttributes->setDefensePower(defensePower);
        pSkillAttributes->setFightDistance(fightDistance);
    }


    /**
    * @brief 技能信息显示
    * @return None
    */
    void dispaly(void)
    {
        printf("人物: %s\n", m_name);
        printf("战斗力: %d\n", pSkillAttributes->getCombatPower());
        printf("防御力: %d\n", pSkillAttributes->getDefensePower());
        printf("飞行距离: %d\n", pSkillAttributes->getFightDistance());
        printf("\n");
    }

    /**
      * @brief    人物克隆
      * @return   克隆后的人物信息
      */
    virtual CCharacter *clone()
    {
        if (m_numLimit < 3)
        {
            m_numLimit++;
            printf("\e[%d;%dm%s\e[0m\n", 1, 36, "克隆人物信息");
            return new CCharacter(*this);
        }
        else
        {
            return NULL;
        }
    }
};

3、构造并克隆人物实现

int main()
{
    CCharacter *pCharacter = new CCharacter("孙悟空", 2000, 1000, 18000);
    pCharacter->dispaly();

    CCharacter *pArrCharacter[5] = {NULL};


    for (int i = 0; i < 5; i++)
    {
        pArrCharacter[i] = pCharacter->clone();

        if (pArrCharacter[i] != NULL)
        {
           pArrCharacter[i]->dispaly();
        }
        else
        {
            printf("\e[%d;%dm%s\e[0m\n", 1, 31, "分身失败");
            break;
        }
    }

    printf("--------------分身完成后---------------\n");

    pCharacter->dispaly();

    for (int i = 0; i < 5; i++)
    {
        if (pArrCharacter[i] != NULL)
        {
           pArrCharacter[i]->dispaly();
        }
    }

    pCharacter->setSkillAttributes(1800, 900, 15000);

    printf("--------------本体属性提升后---------------\n");

    pCharacter->dispaly();

    for (int i = 0; i < 5; i++)
    {
        if (pArrCharacter[i] != NULL)
        {
           pArrCharacter[i]->dispaly();
        }
    }

    return 0;
}

4、运行结果

构造人物信息[孙悟空]
人物: 孙悟空
战斗力: 2000
防御力: 1000
飞行距离: 18000

克隆人物信息
复制人物信息
人物: 孙悟空分身1
战斗力: 1600
防御力: 800
飞行距离: 14400

克隆人物信息
复制人物信息
人物: 孙悟空分身2
战斗力: 1440
防御力: 720
飞行距离: 12960

克隆人物信息
复制人物信息
人物: 孙悟空分身3
战斗力: 1296
防御力: 648
飞行距离: 11664

分身失败
--------------分身完成后---------------
人物: 孙悟空
战斗力: 1458
防御力: 729
飞行距离: 13122

人物: 孙悟空分身1
战斗力: 1600
防御力: 800
飞行距离: 14400

人物: 孙悟空分身2
战斗力: 1440
防御力: 720
飞行距离: 12960

人物: 孙悟空分身3
战斗力: 1296
防御力: 648
飞行距离: 11664

--------------本体属性提升后---------------
人物: 孙悟空
战斗力: 1800
防御力: 900
飞行距离: 15000

人物: 孙悟空分身1
战斗力: 1600
防御力: 800
飞行距离: 14400

人物: 孙悟空分身2
战斗力: 1440
防御力: 720
飞行距离: 12960

人物: 孙悟空分身3
战斗力: 1296
防御力: 648
飞行距离: 11664

5、从运行结果来看,受功能点 3 的影响,经过最大次数的分身后,分身的属性个不一致,甚至孙悟空的技能属性比第一个分身还低(完全能被分身打败替换啊,哈哈哈),因此需要调整功能点3的要求:

复制后的分身各属性为本体的 80%,同时每次分身都会导致本体降低 10% 的属性,本体的属性降低会影响所有分身,即无论本体属性是多少,分身的属性均为本体当前属性的 80%

根据功能要求变更后需要对代码进行改动
 
    /**
      * @brief     拷贝构造函数
      * @param[in] src 类
      * @return None
      */
    CCharacter(CCharacter &src)
    {
        printf("\e[%d;%dm%s\e[0m\n", 1, 32, "复制人物信息");

        pSkillAttributes = src.pSkillAttributes; // 浅拷贝

        sprintf(m_name, "%s分身%d", src.m_name, src.m_numLimit);
        isSelf = false; // 标记分身

        /* 本身为原来的 90% 的属性 */
        src.pSkillAttributes->setCombatPower(src.pSkillAttributes->getCombatPower() * 0.9);
        src.pSkillAttributes->setDefensePower(src.pSkillAttributes->getDefensePower() * 0.9);
        src.pSkillAttributes->setFightDistance(src.pSkillAttributes->getFightDistance() * 0.9);
    }

    /**
     * @brief     设置技能属性
     * @param[in] combatPower     战斗力值
     * @param[in] defensePower    防御力值
     * @param[in] fightDistance   飞行距离
     * @return None
     */
    void setSkillAttributes(int combatPower, int defensePower, int fightDistance)
    {
        if (isSelf) // 只有本体才能改变属性
        {
            pSkillAttributes->setCombatPower(combatPower);
            pSkillAttributes->setDefensePower(defensePower);
            pSkillAttributes->setFightDistance(fightDistance);
        }
    }

    /**
    * @brief 技能信息显示
    * @return None
    */
    void dispaly(void)
    {
        int combatPower = pSkillAttributes->getCombatPower(); // 战斗力
        int defensePower = pSkillAttributes->getDefensePower(); // 防御力
        int fightDistance = pSkillAttributes->getFightDistance(); // 分行距离

        if (!isSelf)
        {
            /* 分身的属性为本体的 80% */
            combatPower *= 0.8;
            defensePower *= 0.8;
            fightDistance *= 0.8;
        }

        printf("人物: %s\n", m_name);
        printf("战斗力: %d\n", combatPower);
        printf("防御力: %d\n", defensePower);
        printf("飞行距离: %d\n", fightDistance);
        printf("\n");
    }

6、运行结果,从结果来看,孙悟空的分身属性都一致,且均比本体属性要低

构造人物信息[孙悟空]
人物: 孙悟空
战斗力: 2000
防御力: 1000
飞行距离: 18000

克隆人物信息
复制人物信息
人物: 孙悟空分身1
战斗力: 1440
防御力: 720
飞行距离: 12960

克隆人物信息
复制人物信息
人物: 孙悟空分身2
战斗力: 1296
防御力: 648
飞行距离: 11664

克隆人物信息
复制人物信息
人物: 孙悟空分身3
战斗力: 1166
防御力: 583
飞行距离: 10497

分身失败
--------------分身完成后---------------
人物: 孙悟空
战斗力: 1458
防御力: 729
飞行距离: 13122

人物: 孙悟空分身1
战斗力: 1166
防御力: 583
飞行距离: 10497

人物: 孙悟空分身2
战斗力: 1166
防御力: 583
飞行距离: 10497

人物: 孙悟空分身3
战斗力: 1166
防御力: 583
飞行距离: 10497

--------------本体属性提升后---------------
人物: 孙悟空
战斗力: 1800
防御力: 900
飞行距离: 15000

人物: 孙悟空分身1
战斗力: 1440
防御力: 720
飞行距离: 12000

人物: 孙悟空分身2
战斗力: 1440
防御力: 720
飞行距离: 12000

人物: 孙悟空分身3
战斗力: 1440
防御力: 720
飞行距离: 12000


疑问

1、对于拷贝构造函数而言,可以不定义该函数,使用C++类默认特殊的拷贝构造函数(浅拷贝),不定义拷贝构造函数也能实现上述第五点功能变更的要求,那为啥还需要定义拷贝构造函数呢?

定义拷贝构造函数可以实现默认拷贝构造函数不具备的功能,如分身降低本体 10% 的属性功能,当然这个功能可以在main函数中重新实现;这属于功能划分不明确,没有隐藏实现细节等

2、通过以下代码也能实现人物分身功能,为什么需要定义克隆函数(virtual CCharacter *clone())呢?

int main()
{
    CCharacter *pCharacter = new CCharacter("孙悟空", 2000, 1000, 18000);
    pCharacter->dispaly();

    CCharacter arrCharacter[3];

    for (int i = 0; i < 3; i++)
    {
        arrCharacter[i] = *pCharacter;
        arrCharacter[i].dispaly();
    }

    ...

    return 0;
}

这属于功能划分不明确,定义克隆函数的目的也是为了隐藏实现细节,如三次分身限制等,减少克隆分身的复杂性

3、为什么更改后的需求设置技能属性需要加上限制条件?
 
    /**
     * @brief     设置技能属性
     * @param[in] combatPower     战斗力值
     * @param[in] defensePower    防御力值
     * @param[in] fightDistance   飞行距离
     * @return None
     */
    void setSkillAttributes(int combatPower, int defensePower, int fightDistance)
    {
        if (isSelf) // 只有本体才能改变属性
        {
            pSkillAttributes->setCombatPower(combatPower);
            pSkillAttributes->setDefensePower(defensePower);
            pSkillAttributes->setFightDistance(fightDistance);
        }
    }

由于这是浅拷贝,若不加限制,则通过克隆分身也能修改本体的属性,而分身和本体属性不同,若是权限放开,则会引起不必要的误解

浅拷贝(分身依赖,受本体属性影响)
深拷贝(分身独立,不受本体属性影响)
 
 
 
 
 
 
 
 
posted @ 2022-06-10 19:04  大橙子疯  阅读(171)  评论(0编辑  收藏  举报