设计模式学习(七) 原型模式
这是个人学习编程模式的系列学习笔记第七篇。
采用Qt Creator进行编写,但尽量采用C++基础语法。
原型模式(Prototype Pattern):用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
这种模式是说用对象创建对象,即克隆对象。还一些支持原型的语言中,这个模式本身支持。对于C++这样的静态语言,实现上是实现一个克隆函数。C++本身默认会创建一个拷贝构造函数,但实现的是位拷贝,属于浅拷贝。在对象本身包含new这类模式创建的成员时,采用默认的拷贝构造函数不能真正克隆一个新的对象(比如,如果有类作为对象的成员,位拷贝情况下,只拷贝了对象的指针,没有复制一份对象的内容,新拷贝构造的对象的成员变量中的对象指针指向的都是同一个内存空间,如果这这个成员有修改,全部都受到影响)。克隆函数需要解决这个问题,采用深拷贝的模式复制这类new模式生成的对象成员。在C++中就是手动实现拷贝构造函数。
按《设计模式:可复用面向对象软件基础》一书的说法,原型模式主要用于以下几种情况:
1、当一个系统应该独立于它的产品创建、构成和表示时
2、 当要实例化的类是在运行时刻指定时,例如,通过动态装载
3、为了避免创建一个与产品类层次平行的工厂类层次时
4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
对于这段描述,不是很理解具体含义。根据大话编程模式及其他文章的说明,个人可以理解应用的主要是这样几种情况下使用原型模式:
1、需要实例化较多的对象,这些对象的实例化参数比较麻烦或耗时,示例化过程使用的参数又一样,这时可以用对象克隆的模式,实例化一个对象后,以后就克隆而不是实例化。
2、一个类的实例可能就几种状态,这些状态又多次要使用,就可以提前将这几个状态的对象实例化放这儿,需要的时候直接克隆。
3、工厂类创建不同具体的产品类时,如果不想创建工厂这一层(多这一层结构比较复杂),可以采用原型模式来生产产品。
具体是实现是,定义一个原型类作为克隆接口类,然后派生具体的产品类,这些产品类实现具体的克隆函数。重点是实现正确的克隆过程。
对于原型模式而言,难点在于如果需要克隆的对象有循环引用,克隆过程复杂,容易出问题。
场景描述
有一个糕点厂,可以生产蛋糕、面包等糕点。传统做法是,蛋糕有蛋糕的模具、面包有面包的模具。根据配方加入不同的配料,混合、搅拌发酵烘焙等过程,出来具体的蛋糕或面包。
后来科技发达了,有了3D打印技术,只需要给一个蛋糕,就可以直接打印出来一个一样的蛋糕,就不需要做蛋糕的这个过程了。
我们要用原型技术来实现这个3D打印蛋糕的功能。
设计思路
定义一个原型接口。3D打印接口(克隆)。定义派生类,蛋糕、面包,继承这个接口。在产品类的克隆方法实现具体的克隆内容。
如果只有一个产品类,就无需继承这个原型接口,直接定义一个克隆方法就可以。
UML
代码
这里只定义的蛋糕一个具体的产品。如果要实现面包是类似的。
#include <iostream>
using namespace std;
class Material
{
public:
Material(int flour, int water, int egg):m_flour(flour),m_water(water),m_egg(egg) {}
int m_flour;
int m_water;
int m_egg;
};
class Prototype
{
public:
Material* m_material = 0;
public:
virtual Prototype* clone() = 0;
virtual void show() = 0;
virtual ~Prototype() {delete m_material;}
};
class Cake : public Prototype
{
public:
Cake(Material* m) {m_material = m;}
Cake(const Cake &cake)
{ //调用Material的拷贝构造函数完成m_material的拷贝,这里用的默认拷贝构造函数是因为都是基本数据类型
Prototype::m_material = new Material(*(cake.m_material));
}
Prototype* clone()
{
return new Cake(*this);
}
void show()
{
cout<<"This is a cake!\n";
cout<<"Flour: "<<m_material->m_flour<<"g\n";
cout<<"Water: "<<m_material->m_water<<"g\n";
cout<<"Egg: "<<m_material->m_egg<<"g\n";
}
};
int main()
{
Prototype* c1 = new Cake(new Material(100, 40, 20));
Prototype* c2 = c1->clone();
c2->m_material->m_flour = 200;
c1->show();
c2->show();
delete c1;
delete c2;
return 0;
}