定义
- 原型模式指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
- 不需要知道任何创建的细节,不调用构造函数
类型
创建型
适用场景
- 类初始化消耗较多的资源
- new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
- 构造函数比较复杂
- 循环体种产生大量对象
优点
- 原型模式性能比直接new一个对象性能高
- 简化创建过程
缺点
- 必须配备克隆方法,重写Object的clone()
- 对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引人风险
- 深拷贝、浅拷贝要运用得当,否则也很容易引人风险。
扩展
- 深克隆(对应引用类型,如果需要他们指向不同的对象,一定用深克隆)
- 浅克隆
代码示例
模拟一个场景,假设我们要给人家发送邮件
- 创建一个邮件类
public class Mail {
private String name;
private String emailAddress;
private String content;
private Date sendTime;
public Mail() {
System.out.println("Mail Class Constructor");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getSendTime() {
return sendTime;
}
public void setSendTime(Date sendTime) {
this.sendTime = sendTime;
}
@Override
public String toString() {
return "Mail{" +
"name='" + name + '\'' +
", emailAddress='" + emailAddress + '\'' +
", content='" + content + '\'' +
", sendTime=" + sendTime +
'}';
}
}
- 创建邮件工具类
public class MailUtil {
public static void sendMail(Mail mail) {
String outputContent = "时间:{0},向{1},邮件地址:{2},邮件内容:{3},发送邮件";
System.out.println(MessageFormat.format(outputContent, mail.getSendTime(), mail.getName(), mail.getEmailAddress(), mail.getContent()));
}
public static void saveOriginMailRecord(Mail mail){
System.out.println("存储原始邮件记录内容,"+mail.getContent());
}
}
- 一般调用模拟
public class Test {
public static void main(String[] args) {
Mail mail = new Mail();
mail.setContent("初始化邮件模板");
for (int i = 0; i < 2; i++) {
mail.setName("姓名"+i);
mail.setEmailAddress("姓名"+i+"@oiobee.com");
mail.setContent("哈喽你好");
mail.setSendTime(new Date(0L));
MailUtil.sendMail(mail);
}
MailUtil.saveOriginMailRecord(mail);
}
}
执行结果
Mail Class Constructor
时间:70-1-1 上午8:00,向姓名0,邮件地址:姓名0@oiobee.com,邮件内容:哈喽你好,发送邮件
时间:70-1-1 上午8:00,向姓名1,邮件地址:姓名1@oiobee.com,邮件内容:哈喽你好,发送邮件
存储原始邮件记录内容,哈喽你好
假设我们在发送完邮件后,需要保存邮件模板我们发现这样的方式,是不可以的,现在存储的是for循环中最后一次set的结果
现在呢,我们就引人原型模式看一下如何解决这个问题
原型模式:是在内存中进行二进制流的拷贝,比new一个对象的性能要好一下。假设我们mail的对象初始化非常复杂,并且在for循环中会产生大量的对象,在这种情况下用原型模式是非常合适的。
步骤
- 需要将Mail类具有克隆功能,具体做法就是让该类实现Cloneable接口,然后重写clone()
//1.实现ICloneable
//2.重写父类clone()
public class Mail implements Cloneable{
private String name;
private String emailAddress;
private String content;
private Date sendTime;
public Mail() {
System.out.println("Mail Class Constructor");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getSendTime() {
return sendTime;
}
public void setSendTime(Date sendTime) {
this.sendTime = sendTime;
}
@Override
public String toString() {
return "Mail{" +
"name='" + name + '\'' +
", emailAddress='" + emailAddress + '\'' +
", content='" + content + '\'' +
", sendTime=" + sendTime +
'}' + super.toString();
}
@Override
protected Object clone() throws CloneNotSupportedException {
//为了方便调试
System.out.println("clone mail object");
return super.clone();
}
}
- 修改调用
在for 循环中使用mail的clone()
public class Test {
public static void main(String[] args) throws Exception{
Mail mail = new Mail();
mail.setContent("初始化邮件模板");
System.out.println("mail"+mail.toString());
for (int i = 0; i < 2; i++) {
Mail mailTemp = (Mail)mail.clone();
mailTemp.setName("姓名"+i);
mailTemp.setEmailAddress("姓名"+i+"@oiobee.com");
mailTemp.setContent("哈喽你好");
mailTemp.setSendTime(new Date(0L));
MailUtil.sendMail(mailTemp);
System.out.println("mailTemp"+mailTemp.toString());
}
MailUtil.saveOriginMailRecord(mail);
}
}
执行结果
Mail Class Constructor
mailMail{name='null', emailAddress='null', content='初始化邮件模板', sendTime=null}com.test.principle.pattern.creational.prototype.Mail@45ee12a7
clone mail object
时间:70-1-1 上午8:00,向姓名0,邮件地址:姓名0@oiobee.com,邮件内容:哈喽你好,发送邮件
mailTempMail{name='姓名0', emailAddress='姓名0@oiobee.com', content='哈喽你好', sendTime=Thu Jan 01 08:00:00 CST 1970}com.test.principle.pattern.creational.prototype.Mail@355da254
clone mail object
时间:70-1-1 上午8:00,向姓名1,邮件地址:姓名1@oiobee.com,邮件内容:哈喽你好,发送邮件
mailTempMail{name='姓名1', emailAddress='姓名1@oiobee.com', content='哈喽你好', sendTime=Thu Jan 01 08:00:00 CST 1970}com.test.principle.pattern.creational.prototype.Mail@4dc63996
存储原始邮件记录内容,初始化邮件模板
通过结果我们可以看出,满足了我们的要求,我们还发现调用clone方法的是,是不会调用Mail的构造器的。并且我们通过super.toString(),发现们在克隆出来的对象并不是同一个
踩坑
上代码
public class Test {
public static void main(String[] args) throws Exception{
//初始化一个时间
Date time = new Date(0L);
//初始化一个mail实例
Mail mail = new Mail();
mail.setName("姓名A");
mail.setSendTime(time);
//克隆一个mail实例
Mail mailTemp = (Mail)mail.clone();
//修改时间
mailTemp.getSendTime().setTime(666666666666L);
//打印两个实例内容
System.out.println("mail"+mail.toString());
System.out.println("mailTemp"+mailTemp.toString());
//查看sendtime的hashCode
System.out.println(mail.getSendTime().hashCode());
System.out.println(mailTemp.getSendTime().hashCode());
}
}
执行结果
Mail Class Constructor
clone mail object
mailMail{name='姓名A', emailAddress='null', content='null', sendTime=Sat Feb 16 09:11:06 CST 1991}com.test.principle.pattern.creational.prototype.Mail@7ea987ac
mailTempMail{name='姓名A', emailAddress='null', content='null', sendTime=Sat Feb 16 09:11:06 CST 1991}com.test.principle.pattern.creational.prototype.Mail@12a3a380
946735665
946735665
我们预期的结果是mail的SendTime 和 mailTemp是两个不相同的时间。但是结果却让我大失所望,两个时间竟然都变成了修改后的时间,我们通过sendTime的hashCode可以看出这个Date对象是同一个。还记得我们最开始的时候说的对应引用类型,如果需要他们指向不同的对象,一定用深克隆。所以要记住
好的现在我们修改一下我们mail的clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Mail mail = (Mail)super.clone();
//对引用对象 深克隆(针对引用类型)
mail.sendTime = (Date)mail.sendTime.clone();
return mail;
}
再次执行测试代码,执行结果
Mail Class Constructor
mailMail{name='姓名A', emailAddress='null', content='null', sendTime=Thu Jan 01 08:00:00 CST 1970}com.test.principle.pattern.creational.prototype.Mail@7ea987ac
mailTempMail{name='姓名A', emailAddress='null', content='null', sendTime=Sat Feb 16 09:11:06 CST 1991}com.test.principle.pattern.creational.prototype.Mail@12a3a380
0
946735665
这样从结果上看就是正常的了