什么是原型模式
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新对象。
原型模式有一个样板实例,然后通过拷贝对象的形式来生成新对象,这个在jdk中有个克隆对象,从而避免new对象。
原型模式的适用场景
类初始化需要很多资源,通过拷贝避免这些资源引用的资源消耗。
new对象繁琐,或需要很多数据准备或权限审批。
一个对象需要作为模板功其他多个对象访问并对其修改时,可拷贝原对象来实现一对一修改。
需要注意的是jdk通过实现Cloneable接口调用clone函数来实现克隆来说并不一定比new对象的速度快,推荐使用原型模式的场景主要准对new 对象消耗大量资源情况下。
原型模式用例
上机考试,试卷都一样,假如试卷实例化要消耗大量资源,比如获取可用内存,初始化数据库,获取试卷类型,其他等等。如果我们考试的题目是一样的,获取题目等操作就不必重复获取,只需要获取一次成功后复制题目给其他所有同学,不必每个同学都要连接到题库,初始化其他等资源。如果太多同学同时操作,这样数据库会处于高负荷状态,可能出现差错。
原型模式UML类图
类ExamPaper:
public class ExamPaper implements Cloneable {
private String name;
private String id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
protected ExamPaper clone(){
try{
ExamPaper paper = (ExamPaper) super.clone();
paper.name = this.name;
paper.id = this.id;
return paper;
}catch(Exception e){
}
return null;
}
public void showPaper(){
System.out.println("-------------试卷开始---------------");
System.out.println("高等数学试卷");
System.out.println("确认您的姓名:"+name);
System.out.println("确认您的学号:"+id);
System.out.println("如有疑问请报告监考教师,无疑问将开始上机考试");
System.out.println("您的试题如下:");
System.out.println("第一题.....");
System.out.println("第二题.....");
System.out.println("---------------试卷结束-------------");
}
}
测试类:
public class Test {
public static void main(String[] args) {
//模板试卷
ExamPaper paper = new ExamPaper();
//第一份试卷
paper.setName("张三");
paper.setId("20147760645");
paper.showPaper();
//第二份试卷
ExamPaper paper2 = paper.clone();
paper2.setName("李四");
paper2.setId("1324444");
paper2.showPaper();
}
}
测试结果:
-------------试卷开始---------------
高等数学试卷
确认您的姓名:张三
确认您的学号:20147760645
如有疑问请报告监考教师,无疑问将开始上机考试
您的试题如下:
第一题.....
第二题.....
---------------试卷结束-------------
-------------试卷开始---------------
高等数学试卷
确认您的姓名:李四
确认您的学号:1324444
如有疑问请报告监考教师,无疑问将开始上机考试
您的试题如下:
第一题.....
第二题.....
---------------试卷结束-------------
原型模式总结
原型模式比较简单,我们只需要实现cloneable接口和clone函数即可复制一份原对象,从而对其进行必要的修改来实现自己的目的。需要注意的是使用clone函数复制对象不会执行原对象的构造函数,如果你需要在构造函数中做一些其他的事情,就需要考虑一下了。
优点:原型模式内存中拷贝为二进制流的形式拷贝,比直接new对象好很多,但是代码量也提升了不少。
缺点:直接在内存中拷贝,构造函数不会执行,缺少约束。
原型模式中的浅拷贝与深拷贝
模式说完了,但是会不会有些许问题呢?上边我们操作的是基本类型String对象,我们如果操作非基本类型再来看看结果吧。
修改
ExamPaper类:我们所做的修改就是加入了非基本数据类型的数据操作输出。
public class ExamPaper implements Cloneable {
private String name;
private String id;
//增加了非基本数据类型
public ArrayList<String> array = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
protected ExamPaper clone(){
try{
ExamPaper paper = (ExamPaper) super.clone();
paper.name = this.name;
paper.id = this.id;
//修改前:直接赋值array
paper.array = this.array;
return paper;
}catch(Exception e){
}
return null;
}
public void showPaper(){
System.out.println("-------------试卷开始---------------");
System.out.println("高等数学试卷");
System.out.println("确认您的姓名:"+name);
System.out.println("确认您的学号:"+id);
for(String number:array){
System.out.println("测试数据"+number);
};
System.out.println("如有疑问请报告监考教师,无疑问将开始上机考试");
System.out.println("您的试题如下:");
System.out.println("第一题.....");
System.out.println("第二题.....");
System.out.println("---------------试卷结束-------------");
}
}
测试类:
public class Test {
public static void main(String[] args) {
//模板试卷
ExamPaper paper = new ExamPaper();
//第一份试卷
paper.setName("张三");
paper.setId("20147760645");
paper.array.add("123");
paper.showPaper();
//第二份试卷
ExamPaper paper2 = paper.clone();
paper2.setName("李四");
paper2.setId("1324444");
paper2.array.add("232323232323");
paper2.showPaper();
paper.showPaper();
}
}
运行结果:
-------------试卷开始---------------
高等数学试卷
确认您的姓名:张三
确认您的学号:20147760645
测试数据123
如有疑问请报告监考教师,无疑问将开始上机考试
您的试题如下:
第一题.....
第二题.....
---------------试卷结束-------------
-------------试卷开始---------------
高等数学试卷
确认您的姓名:李四
确认您的学号:1324444
测试数据123
测试数据232323232323
如有疑问请报告监考教师,无疑问将开始上机考试
您的试题如下:
第一题.....
第二题.....
---------------试卷结束-------------
-------------试卷开始---------------
高等数学试卷
确认您的姓名:张三
确认您的学号:20147760645
测试数据123
测试数据232323232323
如有疑问请报告监考教师,无疑问将开始上机考试
您的试题如下:
第一题.....
第二题.....
---------------试卷结束-------------
从结果可以看到,我们修改过第二份试卷后,第一份试卷的arraylist值也随着第二份的更改而更改了,而基本类型的数据并没有问题。
改进方法,把非基本类型的数据也实现克隆方法
public class ExamPaper implements Cloneable {
private String name;
private String id;
//增加了非基本数据类型
public ArrayList<String> array = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
protected ExamPaper clone(){
try{
ExamPaper paper = (ExamPaper) super.clone();
paper.name = this.name;
paper.id = this.id;
//修改后:clone数组为新对象
paper.array = (ArrayList<String>) this.array.clone();
return paper;
}catch(Exception e){
}
return null;
}
public void showPaper(){
System.out.println("-------------试卷开始---------------");
System.out.println("高等数学试卷");
System.out.println("确认您的姓名:"+name);
System.out.println("确认您的学号:"+id);
for(String number:array){
System.out.println("测试数据"+number);
};
System.out.println("如有疑问请报告监考教师,无疑问将开始上机考试");
System.out.println("您的试题如下:");
System.out.println("第一题.....");
System.out.println("第二题.....");
System.out.println("---------------试卷结束-------------");
}
}
这次在运行,试卷一的ArrayList的值就不会随着试卷二的更改而更改了。实际开发中我们应该意识到这个问题。
关于深拷贝和浅拷贝
浅拷贝指的是拷贝地址引用,深拷贝涉及新建对象再拷贝信息。浅拷贝就像是我们在电脑中创建了好多快捷方式均指向D盘下某个文件,深拷贝指的是我们把这个文件复制到了某个地方。浅拷贝共用一份实体,深拷贝相互独立。
为什么对于基本数据类型修改没有影响,而对于非基本数据类型就变样了呢?
我们要知道非基本数据类型保存的是基本数据类型的对象的引用地址,而不是实实在在的对象,所以对于基本类型和非基本类型,他们拷贝的规则都是一样的,但对于得基本数据类型,拷贝的是数据地址引用,拷贝好还是引用,指向还是同一对象,而基本类型则没有问题,拷贝来的是对象。所以拷贝非基本类型时要注意使用深拷贝,也就是按照原地址对象重新克隆一遍包含对象而不仅仅是地址引用。