我们在白板上绘制图形,经常使用复制粘贴功能,Clone得到的新图形与原图形完全相同,那么软件设计中该如何实现比较好呢?
上一节提到的工厂方法模式可以通过new新的对象来创建图形,创建之后需要根据复制对象的属性设置新的属性,达到相同的目标,显然该方法有点繁琐,如果原图形对象有一个Clone 接口,调用后直接将复制自身一份岂不更简单?
原型模式Prototype 介绍
软件工程中,提出了原型模式的方法,其主要思想是通过复制(克隆、拷贝)一个指定类型的对象来创建更多同类型的对象,其实现一般是采用对象的内部提供克隆的方法,调用该方法返回一个对象的副本。被复制的对象称为“原型”对象,通过复制该“原型”对象产生更多同类型的对象。
原型模式创建对象的方式,与上一节的工厂模式是不同的,工厂模式通过工厂封装具体的new操作的过程返回一个新的对象,但有些情况下,通过创建工厂创建对象的方式不划算,反而使用原型模式更简单高效。比如下面场景:
- 需要一个对象在某个状态下的副本,使用原型模式Clone是最好的选择。
- 对象之间的区别很小,可能就几个属性不同,使用原型模式可省去创建对象调用构造的麻烦。
- 实例化的对象类型不能在开始时确定,而是在运行期确定,那么使用这个类型的对象克隆出一个新的对象比较容易一些。
- 原型模式使得克隆一个原型而不是请求一个工厂方法产生一个新的对象,不需要Creator类层次。
实际使用中,可以借助于原型管理器(PrototypeManager),创建具体原型类的对象,并记录每一个被创建的对象。并且若系统中原型数目不固定时,可以动态的创建和销毁对象。
原型模式(Prototype)UML图
原型模式(Prototype)代码示例
typedef unsigned int FigureID;
typedef struct FigurePos{
float x;
float y;
};
class WBFigure
{
public:
WBFigure(){ }
WBFigure(char* name, FigureID uFigureId){ }
virtual ~WBFigure(){}
public:
virtual WBFigure* Clone() = ;
virtual void SetName(char* name)
{
if(name == NULL)
{
m_name = new char[];
strcpy(this->m_name, "");
}
else
{
m_name = new char[strlen(name)+];
strcpy(m_name, name);
}
}
virtual char* GetName()
{
return m_name;
}
virtual void SetFigureID(FigureID uFigureId)
{
m_uFigureID = uFigureId;
}
virtual FigureID GetFigureID()
{
return m_uFigureID;
}
virtual void Paint(FigurePos postion) = ;
protected:
char* m_name;
FigureID m_uFigureID;
};
//concrete Prototype A
class RectFigure: public WBFigure
{
public:
RectFigure(char* name, FigureID uFigureId)
{
if(name == NULL)
{
m_name = new char[];
strcpy(this->m_name, "");
}
else
{
m_name = new char[strlen(name)+];
strcpy(m_name, name);
}
m_uFigureID = uFigureId;
}
RectFigure(const RectFigure& figure)
{
m_name = new char[strlen(figure.m_name)+];
strcpy(m_name, figure.m_name);
m_uFigureID = figure.m_uFigureID;
}
~RectFigure()
{
delete[] m_name;
m_uFigureID = ;
}
WBFigure* Clone()
{
return new RectFigure(*this);
}
void Paint(FigurePos postion)
{
//TODO: paint picture in the position
}
};
//concrete Prototype B
class TriFigure: public WBFigure
{
public:
TriFigure(char* name, FigureID uFigureId)
{
if(name == NULL)
{
m_name = new char[];
strcpy(this->m_name, "");
}
else
{
m_name = new char[strlen(name)+];
strcpy(m_name, name);
}
m_uFigureID = figure.m_uFigureID;
}
TriFigure(const TriFigure& figure)
{
m_name = new char[strlen(figure.m_name)+];
strcpy(m_name, figure.m_name);
m_uFigureID = figure.m_uFigureID;
}
~TriFigure()
{
delete[] m_name;
m_uFigureID = ;
}
WBFigure* Clone()
{
return new TriFigure(*this);
}
void Paint(FigurePos postion)
{
//TODO: paint picture in the position
}
};
//Prototype Manager
class WBFigureManager
{
public:
WBFigureManager(){}
~WBFigureManager(){}
private:
std::map<FigureID, WBFigure*> m_FigureMgr;
public:
void Add(WBFigure* figure)
{
m_FigureMgr.insert(std::pair<FigureID, WBFigure*>(figure.GetFigureID(), figure));
}
WBFigure* Get(FigureID uFigureId)
{
std::map<FigureID, WBFigure*>::iterator itor = m_FigureMgr.find(uFigureId);
if(itor!= m_FigureMgr.end())
return itor->second;
return NULL;
}
};
//Client 端使用方式
WBFigureManager *pManager = new WBFigureManager();
WBFigure *pFigure1 = new RectFigure("Rectangle", );
WBFigure *pFigure2 = new TriFigure("Triangle", );
pManager->add(pFigure1);
pManager->add(pFigure1);
pManager->get()->Paint(FigurePos(, ));
pManager->get()->Paint(FigurePos(, ));
WBFigure *pFigure3 = pManager->get()->Clone();
WBFigure *pFigure4 = pManager->get()->Clone();
原型模式Prototype的不足之处在于:每一个Prototype的子类都必须实现Clone 操作,这有可能比较困难,尤其当类内部包含一些不支持拷贝或者循环引用的对象时。
说明:关于C++ 对象的Clone 过程存在浅复制和深复制问题,将在另外的文章中介绍。