定义:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
示例一:原型模式(通用版)
1. 类图13-3
2. 代码清单13-3:
class PrototypeClass;
class Cloneable
{
public:
virtual PrototypeClass clone() = 0;
};
class PrototypeClass:public Cloneable
{
public:
virtual PrototypeClass clone()
{
PrototypeClass proto;
//copy
return proto;
}
};
示例二:个性化电子账单
1. 需求说明:
银行发送电子账单的邮件一般是有要求的:
①个性化服务:发过去的邮件上总有一些个人信息,比如姓氏等。
②递送成功率:若大批量地发送邮件会被收房邮件服务器误认为是垃圾邮件,因此在邮件头要增加一些伪造数据,以规避被反垃圾邮件引擎误认为是垃圾邮件
从这两方面考虑,电子账单系统(电子账单系统一般包括:账单分析、账单生成器、广告信管理、发送队列管理、发送机、退信处理、报表管理等)的一个子功能,现在考虑一下广告新这个模块如何开发。既然是广告信,肯定需要一个模板,然后再从数据库中把客户的信息一个一个地取出,放到模板中生成一份完整的邮件,然后扔给发送机进行发送处理
2. 类图13-1
3. 类图说明
AdvTemplate 是广告信的模板,一般都是从数据库取出,生成一个BO或是DTO,我们这里使用一个惊天的值来代表
Mail 类是一封邮件类,发送机发送的就是这个类
4. 代码清单13-1:
********** 1. 发送电子账单,代码清单13-1:***************//
class AdvTemplate
{
public:
AdvTemplate()
{
this->m_advSubject = "The National Day lottery";
this->m_advContext = "***********";
}
QString getAdvSubject()
{
return this->m_advSubject;
}
QString getAdvContext()
{
return this->m_advContext;
}
private:
QString m_advSubject; //title
QString m_advContext; //text
};
class Mail
{
public:
Mail(){}
Mail(AdvTemplate advTemplate)
{
this->m_contxt = advTemplate.getAdvContext();
this->m_subject = advTemplate.getAdvSubject();
}
QString getReceiver()
{
return this->m_receiver;
}
void setReceiver(QString receiver)
{
this->m_receiver = receiver;
}
QString getSubject()
{
return this->m_subject;
}
void setSubject(QString subject)
{
this->m_subject = subject;
}
QString getAppellation()
{
return this->m_appellation;
}
void setAppellation(QString appellation)
{
this->m_appellation = appellation;
}
QString getContxt()
{
return this->m_contxt;
}
void setContxt(QString contxt)
{
this->m_contxt = contxt;
}
QString getTail()
{
return this->m_tail;
}
void setTail(QString tail)
{
this->m_tail = tail;
}
private:
QString m_receiver;
QString m_subject;
QString m_appellation;
QString m_contxt;
QString m_tail;
};
static int MAX_COUNT = 6;
class Client
{
public:
Client()
{
AdvTemplate temp;
this->m_mail = Mail(temp);
this->m_mail.setTail("copyright");
this->m_mail.setAppellation(getString(5));
this->m_mail.setReceiver(getString(5) + "@");
}
void sendMail()
{
for (int i = 0; i < MAX_COUNT; ++i)
{
this->m_mail.setAppellation(getString(5));
this->m_mail.setReceiver(getString(5) + "@");
qDebug() << "title:" + m_mail.getSubject();
qDebug() << "receiver:" + m_mail.getReceiver();
}
}
static QString getString(int maxLength)
{
QString source = "abcdefghijklmn";
QString str = source.left(maxLength);
return str;
}
private:
Mail m_mail;
};
int main()
{
Client client;
client.sendMail();
return 0;
}
5. 代码分析
这是一个线程在运行,也就是发送的是单线程的,那按照一封邮件发出去需要0.02秒,600万封邮件需要33个小时,也就是一整天都发送不完。在此做修改,把sendMail修改为多线程,但是只把sendMail修改为多线程还是有问题,产生第一封邮件对象,放到线程1中运行,还没有发送出去;线程2也启动了,直接把邮件对象mail的收件人地址和称谓修改掉了,线程不安全了。这里我们使用一种新型模式来解决这个问题:通过对象的复制功能来解决。
示例三:修改后的发送电子账单
1. 类图13-2
2. 代码清单13-2:
********** 2. 修改后的发送电子账单,代码清单13-2:***************//
class Mail;
class Cloneable
{
public:
virtual Mail clone() = 0;
};
class AdvTemplate
{
public:
AdvTemplate()
{
this->m_advSubject = "The National Day lottery";
this->m_advContext = "***********";
}
QString getAdvSubject()
{
return this->m_advSubject;
}
QString getAdvContext()
{
return this->m_advContext;
}
private:
QString m_advSubject; //title
QString m_advContext; //text
};
class Mail:public Cloneable
{
public:
Mail(){}
Mail(AdvTemplate advTemplate)
{
this->m_contxt = advTemplate.getAdvContext();
this->m_subject = advTemplate.getAdvSubject();
}
virtual Mail clone()
{
Mail mail;
mail.setAppellation(this->m_appellation);
mail.setContxt(this->m_contxt);
mail.setReceiver(this->m_receiver);
mail.setSubject(this->m_receiver);
mail.setTail(this->m_tail);
return mail;
}
QString getReceiver()
{
return this->m_receiver;
}
void setReceiver(QString receiver)
{
this->m_receiver = receiver;
}
QString getSubject()
{
return this->m_subject;
}
void setSubject(QString subject)
{
this->m_subject = subject;
}
QString getAppellation()
{
return this->m_appellation;
}
void setAppellation(QString appellation)
{
this->m_appellation = appellation;
}
QString getContxt()
{
return this->m_contxt;
}
void setContxt(QString contxt)
{
this->m_contxt = contxt;
}
QString getTail()
{
return this->m_tail;
}
void setTail(QString tail)
{
this->m_tail = tail;
}
private:
QString m_receiver;
QString m_subject;
QString m_appellation;
QString m_contxt;
QString m_tail;
};
static int MAX_COUNT = 6;
class Client
{
public:
Client()
{
AdvTemplate temp;
this->m_mail = Mail(temp);
this->m_mail.setTail("copyright");
this->m_mail.setAppellation(getString(5));
this->m_mail.setReceiver(getString(5) + "@");
}
void sendMail()
{
for (int i = 0; i < MAX_COUNT; ++i)
{
Mail mail = this->m_mail.clone();
mail.setAppellation(getString(5));
mail.setReceiver(getString(5) + "@");
qDebug() << "title:" + mail.getSubject();
qDebug() << "receiver:" + mail.getReceiver();
}
}
static QString getString(int maxLength)
{
QString source = "abcdefghijklmn";
QString str = source.left(maxLength);
return str;
}
private:
Mail m_mail;
};
int main()
{
Client client;
client.sendMail();
return 0;
}
3.代码说明:
在这里即使sendMail即使是多线程也没有关系。在Client类中,mail.clone() 这个方法,把对象复制一份,产生一个新的对象,和原有对象一样,然后再修改细节的数据。不通过new关键字来产生一个对象,而是通过对象复制来实现的模式叫做原型模式。
四、原型模式的应用
1. 优点:
性能优良。原型模式是在内存二进制流的拷贝,比直接new一个对象性能好很多,特别死要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点
逃避构造函数的约束。直接在内存中拷贝,构造函数是不会执行的。
2. 使用场景:
资源优化场景。类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
性能和安全要求的场景。通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
一个对象多个修改者的场景。一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
3. 注意事项:
对象拷贝时构造函数没有被执行,Object类的clone 方法的原理是从内存中(具体地说就是堆内存)以二进制流的方式进行拷贝,重新分配一个内存块,那构造函数没有被执行也很正常了。
五、最佳实践
原型模式先产生出一个包含大量共有信息的类,然后可以拷贝出副本,修正细节信息,建立了一个完整的个性对象。
参考文献《秦小波. 设计模式之禅》(第2版) (华章原创精品) 机械工业出版社