1.模式介绍
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
拷贝是原型模式的精髓所在。
2示例模型
记得上小学的时候,老师把需要做的课外习题写到黑板上,而下面的我们都要把这些题抄 写到自己的本子上,回家做好,第二天交上来,也就是每道题,全班50个人,每个人都要抄写一遍。按照现在的时间理论来说,就是浪费了50个人的时间。但是,那个时候条件限制,老师也是不得已而为之。现在好了,老师做一份电子版的习题,打印一份,然后拿着这份打印的原版,就可以复制出50份。
结合原型模式的概念进行分析,老师打印出来的那一份(已有的对象)就是“原型“,而复制出来的那50份,就是使用的“拷贝。而原型模式就是这么简单的一个道理,通过现有的东西,再复制出一个来。
3.应用场景
(1)新对象与原有对象区别小,只是某些属性不同,新的对象可以通过原型模式对已有对象进行复制来获得,再稍作修改。
(2)当我们的对象类型不是开始就能确定的,而这个类型是在运行期确定的话,那么我们通过这个类型的对象克隆出一个新的对象。(直接使用复制构造函数需要知道对象的类型)
(3)程序运行过程,某个状态下需要一个对象副本,而对象属性有可能在运行过程改变,使用new来创建显然不适合,而且通过 new 产生一个对象需要非常繁琐的数据准备或访问权限。
4.角色结构分析
(1)抽象原型类(AbstractPrototype):规定了具体原型对象必须实现的接口
(2)具体原型类(ConcretePrototype):从抽象原型派生而来,是客户程序使用的,需要实现抽象原型角色所要求的接口
(3)客户端(Client):客户调具体原型对象方法创建一个新的对象
5.应用案例:在已有邮件上修改生成需要发送的新邮件
#include <iostream>
#include <string.h>
using namespace std;
// 抽象原型类
class AbstractPrototype{
public:
virtual AbstractPrototype* clone() = 0;
virtual void show() = 0;
};
//附件类
class Attachment {
private:
string nameAtt; /// 附件名
public:
void changeName(const string& name) {
nameAtt = name;
};
string getName() {
return nameAtt;
}
};
//具体原型类
class ConcretePrototypeMail : public AbstractPrototype {
private:
string mailTitle;
string mailSender;
string mailRecipients;
string mailBody;
Attachment* mailAtta = NULL;
public:
ConcretePrototypeMail(const string& title, const string& sender, const string& rec, const string& body, const string& nameAtt) {
mailTitle = title;
mailSender = sender;
mailRecipients = rec;
mailBody = body;
if (!nameAtt.empty()) {
mailAtta = new Attachment();
mailAtta->changeName(nameAtt);
}
}
ConcretePrototypeMail(const ConcretePrototypeMail& other) {
this->mailTitle = other.mailTitle;
this->mailSender = other.mailSender;
this->mailRecipients = other.mailRecipients;
this->mailBody = other.mailBody;
if (other.mailAtta != NULL) {
mailAtta = new Attachment();
mailAtta->changeName(other.mailAtta->getName());
}
}
~ConcretePrototypeMail(){
if (mailAtta != NULL) {
delete(mailAtta);
}
}
virtual ConcretePrototypeMail* clone() {
ConcretePrototypeMail* newMail = new ConcretePrototypeMail(*this);
return newMail;
}
void show() {
cout << "MailTitle: " << mailTitle << endl;
cout << "MailSender: " << mailSender << endl;
cout << "MailRecipients: " << mailRecipients << endl;
cout << "MailBody: " << mailBody << endl;
cout << "MailAttachment: " << mailAtta->getName() << endl;
}
void changeTitle(string title) {
mailTitle = title;
}
void changeSender(string sender) {
mailSender = sender;
}
void changeRecipients(string rec) {
mailRecipients = rec;
}
void changeBody(string body) {
mailBody = body;
}
void changeAtt(string name) {
if (mailAtta != NULL) {
delete(mailAtta);
}
mailAtta = new Attachment();
mailAtta->changeName(name);
}
};
int main() {
//初始邮件创建
ConcretePrototypeMail* originalMail = new ConcretePrototypeMail("original_title", "original_sender", "original_rec", "original_body", "original_attachment");
cout << "====originalMail====" << endl;
originalMail->show();
// 深拷贝
ConcretePrototypeMail* copyMail = originalMail->clone();
copyMail->changeTitle("copymail_title");
copyMail->changeSender("copymail_sender");
copyMail->changeRecipients("copymail_rec");
copyMail->changeBody("copymail_body");
copyMail->changeAtt("copymail_attachment");
cout << "====copyMail_A====" << endl;
cout << "copyMail_A address: " << copyMail << endl;
copyMail->show();
cout << "====originalMail====" << endl;
originalMail->show();
delete originalMail;
delete copyMail;
return 0;
}
6.模式总结
优点:
(1)当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,提高新实例的创建效率。
(2)扩展性较好,模式中提供了抽象原型类,具体原型类可根据需要扩展。
缺点:
(1)需要为每一个类配备一个克隆方法,该克隆方法位于一个类的内部,改造已有类时需要修改源代码,违背开闭原则;
(2)在实现深克隆时需要编写较为复杂的代码,并且如果对象嵌套很多引用时,为了实现深拷贝每一层嵌套都必须支持深克隆。
注意事项:
(1)拷贝构造函数是核心,而且针对c++要进行的是深拷贝。
(2)克隆函数的关键就是调用拷贝构造函数。