原型模式

原型(Prototype)模式是一种创建型模式。原型模式通过(原型对象)克隆出对个一模一样的对象。实际上,该模式与其说是一种设计模式,不如说是一种创建对象的方法(对象克隆),尤其是创建给定类的对象(实例)过程很复杂(例如,要设置许多成员变量的值)时,适用这种设计模式就比较合适。

引入“原型”(Prototype)模式的定义:用原型实例指定创建对象的类型,并且通过复制这些原型创建新的对象。简单来说,就是通过克隆来创建新的对象实例。

主要组成部分

  • 原型接口 (Prototype Interface):定义一个克隆方法,通常是 clone(),用于复制当前对象的实例。
  • 具体原型类 (Concrete Prototype):实现原型接口,提供具体的克隆实现。每个具体原型类都可以被克隆以创建新对象。
  • 客户端 (Client):使用原型对象来创建新对象。客户端通过调用原型的克隆方法来获得新对象,而不是直接使用构造函数。

原型模式的使用步骤

  1. 定义原型接口

    创建一个包含克隆方法的抽象类 Monster

    //怪物父类
    class Monster
    {
    public:
    	//构造函数
    	Monster(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
    	virtual ~Monster() {} //做父类时析构函数应该为虚函数
    
    public:
    	virtual Monster* clone() = 0; //具体的实现在子类中进行
    
    protected: //可能被子类访问的成员,用protected修饰
    	//怪物属性
    	int m_life;    //生命值
    	int m_magic;   //魔法值
    	int m_attack;  //攻击力
    };
    
  2. 实现具体原型类

    创建具体的怪物类,继承自 Monster,并实现 clone() 方法。

    //亡灵类怪物
    class M_Undead :public Monster
    {
    public:
    	//构造函数
    	M_Undead(int life, int magic, int attack) :Monster(life, magic, attack)
    	{
    		cout << "一只亡灵类怪物来到了这个世界" << endl;
    	}
    
    public:
    	//拷贝构造函数
    	M_Undead(const M_Undead& tmpobj) :Monster(tmpobj)
    	{
    		cout << "调用了M_Undead::M_Undead(const M_Undead& tmpobj)拷贝构造函数创建了一只亡灵类怪物" << endl;
    	}
    
    	virtual Monster* clone()
    	{
    		return new M_Undead(*this); //触发拷贝构造函数的调用来创建亡灵类怪物
    	}
    	//其他代码略....
    };
    
    //元素类怪物
    class M_Element :public Monster
    {
    public:
    	//构造函数
    	M_Element(int life, int magic, int attack) :Monster(life, magic, attack)
    	{
    		cout << "一只元素类怪物来到了这个世界" << endl;
    	}
    
    public:
    	//拷贝构造函数
    	M_Element(const M_Element& tmpobj) :Monster(tmpobj) //初始化列表中注意对父类子对象的初始化
    	{
    		cout << "调用了M_Element::M_Element(const M_Element& tmpobj)拷贝构造函数创建了一只元素类怪物" << endl;
    	}
    
    	virtual Monster* clone()
    	{
    		return new M_Element(*this);
    	}
    	//其他代码略....
    };
    
    //机械类怪物
    class M_Mechanic :public Monster
    {
    public:
    	//构造函数
    	M_Mechanic(int life, int magic, int attack) :Monster(life, magic, attack)
    	{
    		cout << "一只机械类怪物来到了这个世界" << endl;
    	}
    
    public:
    	//拷贝构造函数
    	M_Mechanic(const M_Mechanic& tmpobj) :Monster(tmpobj)
    	{
    		cout << "调用了M_Mechanic::M_Mechanic(const M_Mechanic& tmpobj)拷贝构造函数创建了一只机械类怪物" << endl;
    	}
    
    	virtual Monster* clone()
    	{
    		return new M_Mechanic(*this);
    	}
    	//其他代码略....
    };
    
  3. 使用客户端

    在客户端代码中,创建原型对象并使用克隆方法生成新对象。

    int main()
    {
        // 创建原型对象
        Monster* undead = new M_Undead(100, 50, 20);
        Monster* element = new M_Element(80, 60, 25);
        Monster* mechanic = new M_Mechanic(120, 30, 30);
    
        // 克隆对象
        Monster* clonedUndead = undead->clone();
        Monster* clonedElement = element->clone();
        Monster* clonedMechanic = mechanic->clone();
    
        // 使用克隆对象...
        // 例如,可以在这里对克隆对象进行操作或输出属性
    
        // 清理内存
        delete undead;
        delete element;
        delete mechanic;
        delete clonedUndead;
        delete clonedElement;
        delete clonedMechanic;
    
        return 0;
    }
    

原型模式的 UML 图

C++ 设计模式——原型模式-LMLPHP

原型模式 UML 图解析

  • 类关系
    • Prototype(原型类):这是一个抽象类,定义了克隆接口(通常是 clone() 方法)。在 UML 图中,对应于 Monster 类。
    • ConcretePrototype(具体原型类):这些是实现了 Prototype 接口的具体类,负责实现克隆逻辑。图中的 M_UndeadM_ElementM_Mechanic 是具体原型类。
  • 稳定与变化
    • 稳定部分Prototype 类(如 Monster)通常保持不变,定义了基本的属性和克隆接口。
    • 变化部分ConcretePrototype 类(如 M_UndeadM_Element 等)是变化的部分,可以根据需求添加新的具体类。
  • 信息隐藏
    • 客户端代码与 Prototype 接口交互,而不需要了解具体的实现细节。这种设计实现了信息隐藏,使得系统更加灵活。
  • 扩展性
    • 新的具体原型类可以通过实现 Prototype 接口来扩展,而不需要修改现有的代码结构,符合开闭原则。

优点和缺点

优点

  • 简化对象创建:通过克隆现有对象,避免了复杂的构造过程,尤其是当对象需要大量初始化参数时。
  • 提高灵活性:客户端不需要了解具体的对象创建逻辑,只需使用原型进行克隆,增强了代码的灵活性和可维护性。
  • 支持动态配置:可以在运行时决定克隆哪种类型的对象,适应性强。

缺点

  • 性能开销:克隆对象可能导致较大的内存开销,特别是在对象较大或复杂时。
  • 深拷贝与浅拷贝:需要明确区分深拷贝和浅拷贝,错误的实现可能导致共享状态问题。
  • 增加复杂性:需要实现克隆方法,可能会增加代码的复杂性,特别是在有多个子类时。

适用场景

  • 对象创建过程复杂:当对象的构造过程非常复杂,涉及多个参数初始化时,使用原型模式可以简化这一过程。
  • 需要大量相似对象:当需要创建大量相似对象时,使用克隆原型可以提高效率。
  • 运行时动态创建:当对象类型在运行时动态决定时,原型模式可以提供灵活的解决方案。
  • 避免构造函数的重复调用:在需要频繁创建相似对象的场景中,原型模式可以避免重复调用构造函数,提高性能。

总结

原型模式通过定义 PrototypeConcretePrototype 的关系,使得对象的克隆过程更加灵活和高效。客户端只需依赖于 Prototype 接口,能够动态创建新的对象实例,增强了系统的可扩展性和可维护性。

08-19 17:25