概念
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
解释:
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节
通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建
具体参考孙悟空拔毛复制
推演
需求
学校颁发“三好学生”奖状,基本信息如下:学生姓名(name),学生年级(grade),获奖日期(time),校长电子签名(principal)
其中,所有奖状的获奖日期和校长电子签名都一致,只有学生姓名和学生年级会变
反例(传统思路)
创建奖状对象
@Getter@Setterclass Citation{/*** 学生姓名*/String name;/*** 学生年级*/String grade;/*** 获奖时间*/Date time;/*** 校长电子签名*/String principal;public Citation() {System.out.println("构造对象。。。。");}@Overridepublic String toString() {return "Citation{" +"name='" + name + '\'' +", grade='" + grade + '\'' +", time=" + time +", principal='" + principal + '\'' +'}';}}
颁发奖状逻辑
//给张小明颁奖Citation citation1 = new Citation();citation1.setName("张小明");citation1.setGrade("一年级");citation1.setPrincipal("王校长");citation1.setTime(new Date());System.out.println(citation1);//给李小萌颁奖Citation citation2 = new Citation();citation2.setName("李小萌");citation2.setGrade("二年级");citation2.setPrincipal("王校长");citation2.setTime(new Date());System.out.println(citation2);
运行结果
构造对象。。。。Citation{name='张小明', grade='一年级', time=Fri Oct 11 23:53:54 CST 2019, principal='王校长'}构造对象。。。。Citation{name='李小萌', grade='二年级', time=Fri Oct 11 23:53:54 CST 2019, principal='王校长'}
可以看到,每次颁奖都需要重复设置所有的属性,包括不会变动的属性字段,显然当固定不变的字段多了起来,这种做法会显得十分低效且重复
正例1(原型模式-浅拷贝)
需求**:我们希望,每次重新颁奖仅需要改变变动的量,而不需要重新设置所有属性**
创建奖状对象并实现Cloneable接口,并重写 Object clone()方法,将其修饰符从protected改为public以供调用
@Getter@Setter@ToStringclass Citation implements Cloneable{/*** 学生姓名*/String name;/*** 学生年级*/String grade;/*** 获奖时间*/Date time;/*** 校长电子签名*/String principal;public Citation() {System.out.println("构造对象。。。。");}@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone();}}
颁发奖状逻辑
//给张小明颁奖Citation citation1 = new Citation();citation1.setName("张小明");citation1.setGrade("一年级");citation1.setPrincipal("王校长");citation1.setTime(new Date());System.out.println(citation1);//给李小萌颁奖Citation citation2 = (Citation) citation1.clone();citation2.setName("李小萌");citation2.setGrade("二年级");System.out.println(citation2);
运行结果
构造对象。。。。Citation{name='张小明', grade='一年级', time=Sat Oct 12 00:04:07 CST 2019, principal='王校长'}Citation{name='李小萌', grade='二年级', time=Sat Oct 12 00:04:07 CST 2019, principal='王校长'}
如此,便可减少很多的重复性操作,上述例子中的citation1.clone() 称为浅拷贝,其概念如下:
**
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
如果属性是基本类型,拷贝的就是基本类型的值;
如果属性是内存地址(引用类型),拷贝的就是内存地址
也就是说,浅拷贝是直接复制内存中的二进制,所以效率更高,其原理如下:
**
问题点
1、”原型模式“中拷贝出来的对象是否同一个对象(地址是否一致)?
从上原理图中,我们可以知道,两个对象放在不同的内存地址中(3000和4000),所以地址不一致,是两个不同的对象
即:citation2 == citation1 ——-> false
2、修改两个对象的同一个属性,是否会同步变化?
从上原理图可知,浅拷贝会原原本本的从源对象拷贝过来,且是两个不同的对象。其中如果属性是基本类型,拷贝的就是基本类型的值(如上述的name等等属性);如果属性是内存地址(引用类型),拷贝的就是内存地址(如上述的time属性),故而分情况而论:
如:
修改其共有基本类型**属性principal:
//给张小明颁奖Citation citation1 = new Citation();citation1.setName("张小明");citation1.setGrade("一年级");citation1.setPrincipal("王校长");citation1.setTime(new Date());//给李小萌颁奖Citation citation2 = (Citation) citation1.clone();citation2.setName("李小萌");citation2.setGrade("二年级");//修改属性citation1.setPrincipal("王老五");System.out.println(citation1);System.out.println(citation2);
结果,并未影响到另一个对象
Citation{name='张小明', grade='一年级', time=Sat Oct 12 17:24:54 CST 2019, principal='王老五'}Citation{name='李小萌', grade='二年级', time=Sat Oct 12 17:24:54 CST 2019, principal='王校长'}
修改其共有引用类型属性time
//给张小明颁奖Citation citation1 = new Citation();citation1.setName("张小明");citation1.setGrade("一年级");citation1.setPrincipal("王校长");citation1.setTime(new Date());Thread.sleep(1000);//给李小萌颁奖Citation citation2 = (Citation) citation1.clone();citation2.setName("李小萌");citation2.setGrade("二年级");citation2.setTime(new Date());System.out.println(citation1);System.out.println(citation2);
结果,亦未引起另一对象的属性改变,这是因为没有改变两个对象的共有内存(原理图中的new Date()),而是给citation2的time指定了新的引用地址
那么,我们此时改变内存地址200指向的date的值,会发生什么呢
//给张小明颁奖Citation citation1 = new Citation();citation1.setName("张小明");citation1.setGrade("一年级");citation1.setPrincipal("王校长");citation1.setTime(new Date());Thread.sleep(1000);//给李小萌颁奖Citation citation2 = (Citation) citation1.clone();citation2.setName("李小萌");citation2.setGrade("二年级");//为citation2的time引用设置新的值citation2.getTime().setTime(0);System.out.println(citation1);System.out.println(citation2);
结果如下:
Citation{name='张小明', grade='一年级', time=Thu Jan 01 08:00:00 CST 1970, principal='王校长'}Citation{name='李小萌', grade='二年级', time=Thu Jan 01 08:00:00 CST 1970, principal='王校长'}
这就是浅拷贝的弊端,**引用类型共享地址,共享地址的内容发生变化,将会导致所有对象的更改**
正例2(原型模式-深拷贝):
需求**:我们希望克隆出来的副本对象,无论如何更改,都不会影响到原来的对象**
改写对象的克隆方法:
@Getter@Setter@ToStringclass Citation implements Cloneable{/*** 学生姓名*/String name;/*** 学生年级*/String grade;/*** 获奖时间*/Date time;/*** 校长电子签名*/String principal;@Overridepublic Object clone() throws CloneNotSupportedException {//克隆源对象Citation clone = (Citation) super.clone();//将对象中的引用类型也一并克隆,此时得到的timeClone已经和clone.getTime()得到的time地址不一样了Date timeClone = (Date) clone.getTime().clone();//将新的引用类型地址设置到对应属性中clone.setTime(timeClone);return clone;}}
此时调用克隆方法后设置不同时间已然不会影响其他对象:
//给张小明颁奖Citation citation1 = new Citation();citation1.setName("张小明");citation1.setGrade("一年级");citation1.setPrincipal("王校长");citation1.setTime(new Date());Thread.sleep(1000);//给李小萌颁奖Citation citation2 = (Citation) citation1.clone();citation2.setName("李小萌");citation2.setGrade("二年级");//为citation2的time引用设置新的值citation2.getTime().setTime(0);System.out.println(citation1);System.out.println(citation2);
结果如下,两个时间不同
Citation{name='张小明', grade='一年级', time=Sat Oct 12 20:37:28 CST 2019, principal='王校长'}Citation{name='李小萌', grade='二年级', time=Thu Jan 01 08:00:00 CST 1970, principal='王校长'}
那么,当类中存在多个引用类型属性呢?如果引用类型是个含有引用类型成员的对象呢?即对象深度较深时,不断克隆和设置显得臃肿低效
正例3(原型模式-深拷贝)
需求**:我们希望,可以一步到位地深度克隆,即能够同步克隆所有的引用类型成员而不需要重复克隆
利用序列化天然优势进行深度拷贝,让类再实现可序列化接口Serializable
@Getter@Setter@ToStringclass Citation implements Cloneable,Serializable{/*** 学生姓名*/String name;/*** 学生年级*/String grade;/*** 获奖时间*/Date time;/*** 校长电子签名*/String principal;@Overridepublic Object clone() throws CloneNotSupportedException {//将对象写入内存流ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream oos = null;try {oos = new ObjectOutputStream(out);oos.writeObject(this); //序列化时,对象的所有属性层级关系会被序列化自动处理oos.close();byte[] bytes = out.toByteArray();//将对象从内存中反序列化出来,生成新的对象InputStream in = new ByteArrayInputStream(bytes);ObjectInputStream ois = new ObjectInputStream(in);Object obj = ois.readObject();ois.close();out.close();return obj;} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}}
此时调用克隆方法后设置不同时间已然不会影响其他对象:
//给张小明颁奖Citation citation1 = new Citation();citation1.setName("张小明");citation1.setGrade("一年级");citation1.setPrincipal("王校长");citation1.setTime(new Date());Thread.sleep(1000);//给李小萌颁奖Citation citation2 = (Citation) citation1.clone();citation2.setName("李小萌");citation2.setGrade("二年级");//为citation2的time引用设置新的值citation2.getTime().setTime(0);System.out.println(citation1);System.out.println(citation2);
结果如下,两个时间不同
Citation{name='张小明', grade='一年级', time=Sat Oct 12 20:37:28 CST 2019, principal='王校长'}Citation{name='李小萌', grade='二年级', time=Thu Jan 01 08:00:00 CST 1970, principal='王校长'}
需求完美解决!!!
应用场景
- 对象之间相同或相似,即只是个别的几个属性不同的时候。
- 对象的创建过程比较麻烦,但复制比较简单的时候。
优点
减少了重复代码,提高代码可读性
如有贻误,还请评论指正
