—-慢慢来比较快,虚心学技术—-
**

概念

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

解释
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节

通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建

具体参考孙悟空拔毛复制

推演

需求

学校颁发“三好学生”奖状,基本信息如下:学生姓名(name),学生年级(grade),获奖日期(time),校长电子签名(principal)
其中,所有奖状的获奖日期和校长电子签名都一致,只有学生姓名和学生年级会变

反例(传统思路)

创建奖状对象

  1. @Getter
  2. @Setter
  3. class Citation{
  4. /**
  5. * 学生姓名
  6. */
  7. String name;
  8. /**
  9. * 学生年级
  10. */
  11. String grade;
  12. /**
  13. * 获奖时间
  14. */
  15. Date time;
  16. /**
  17. * 校长电子签名
  18. */
  19. String principal;
  20. public Citation() {
  21. System.out.println("构造对象。。。。");
  22. }
  23. @Override
  24. public String toString() {
  25. return "Citation{" +
  26. "name='" + name + '\'' +
  27. ", grade='" + grade + '\'' +
  28. ", time=" + time +
  29. ", principal='" + principal + '\'' +
  30. '}';
  31. }
  32. }

颁发奖状逻辑

  1. //给张小明颁奖
  2. Citation citation1 = new Citation();
  3. citation1.setName("张小明");
  4. citation1.setGrade("一年级");
  5. citation1.setPrincipal("王校长");
  6. citation1.setTime(new Date());
  7. System.out.println(citation1);
  8. //给李小萌颁奖
  9. Citation citation2 = new Citation();
  10. citation2.setName("李小萌");
  11. citation2.setGrade("二年级");
  12. citation2.setPrincipal("王校长");
  13. citation2.setTime(new Date());
  14. System.out.println(citation2);

运行结果

  1. 构造对象。。。。
  2. Citation{name='张小明', grade='一年级', time=Fri Oct 11 23:53:54 CST 2019, principal='王校长'}
  3. 构造对象。。。。
  4. Citation{name='李小萌', grade='二年级', time=Fri Oct 11 23:53:54 CST 2019, principal='王校长'}

可以看到,每次颁奖都需要重复设置所有的属性,包括不会变动的属性字段,显然当固定不变的字段多了起来,这种做法会显得十分低效且重复

正例1(原型模式-浅拷贝)

需求**:我们希望,每次重新颁奖仅需要改变变动的量,而不需要重新设置所有属性**

创建奖状对象并实现Cloneable接口,并重写 Object clone()方法,将其修饰符从protected改为public以供调用

  1. @Getter
  2. @Setter
  3. @ToString
  4. class Citation implements Cloneable{
  5. /**
  6. * 学生姓名
  7. */
  8. String name;
  9. /**
  10. * 学生年级
  11. */
  12. String grade;
  13. /**
  14. * 获奖时间
  15. */
  16. Date time;
  17. /**
  18. * 校长电子签名
  19. */
  20. String principal;
  21. public Citation() {
  22. System.out.println("构造对象。。。。");
  23. }
  24. @Override
  25. public Object clone() throws CloneNotSupportedException {
  26. return super.clone();
  27. }
  28. }

颁发奖状逻辑

  1. //给张小明颁奖
  2. Citation citation1 = new Citation();
  3. citation1.setName("张小明");
  4. citation1.setGrade("一年级");
  5. citation1.setPrincipal("王校长");
  6. citation1.setTime(new Date());
  7. System.out.println(citation1);
  8. //给李小萌颁奖
  9. Citation citation2 = (Citation) citation1.clone();
  10. citation2.setName("李小萌");
  11. citation2.setGrade("二年级");
  12. System.out.println(citation2);

运行结果

  1. 构造对象。。。。
  2. Citation{name='张小明', grade='一年级', time=Sat Oct 12 00:04:07 CST 2019, principal='王校长'}
  3. Citation{name='李小萌', grade='二年级', time=Sat Oct 12 00:04:07 CST 2019, principal='王校长'}

如此,便可减少很多的重复性操作,上述例子中的citation1.clone() 称为浅拷贝,其概念如下:
**
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
如果属性是基本类型,拷贝的就是基本类型的值;
如果属性是内存地址(引用类型),拷贝的就是内存地址

也就是说,浅拷贝是直接复制内存中的二进制,所以效率更高,其原理如下:
**
图片.png

问题点

1、”原型模式“中拷贝出来的对象是否同一个对象(地址是否一致)?

从上原理图中,我们可以知道,两个对象放在不同的内存地址中(3000和4000),所以地址不一致,是两个不同的对象
即:citation2 == citation1 ——-> false

2、修改两个对象的同一个属性,是否会同步变化?

从上原理图可知,浅拷贝会原原本本的从源对象拷贝过来,且是两个不同的对象。其中如果属性是基本类型,拷贝的就是基本类型的值(如上述的name等等属性);如果属性是内存地址(引用类型),拷贝的就是内存地址(如上述的time属性),故而分情况而论:
如:
修改其共有
基本类型**属性principal:

  1. //给张小明颁奖
  2. Citation citation1 = new Citation();
  3. citation1.setName("张小明");
  4. citation1.setGrade("一年级");
  5. citation1.setPrincipal("王校长");
  6. citation1.setTime(new Date());
  7. //给李小萌颁奖
  8. Citation citation2 = (Citation) citation1.clone();
  9. citation2.setName("李小萌");
  10. citation2.setGrade("二年级");
  11. //修改属性
  12. citation1.setPrincipal("王老五");
  13. System.out.println(citation1);
  14. System.out.println(citation2);

结果,并未影响到另一个对象

  1. Citation{name='张小明', grade='一年级', time=Sat Oct 12 17:24:54 CST 2019, principal='王老五'}
  2. Citation{name='李小萌', grade='二年级', time=Sat Oct 12 17:24:54 CST 2019, principal='王校长'}

修改其共有引用类型属性time

  1. //给张小明颁奖
  2. Citation citation1 = new Citation();
  3. citation1.setName("张小明");
  4. citation1.setGrade("一年级");
  5. citation1.setPrincipal("王校长");
  6. citation1.setTime(new Date());
  7. Thread.sleep(1000);
  8. //给李小萌颁奖
  9. Citation citation2 = (Citation) citation1.clone();
  10. citation2.setName("李小萌");
  11. citation2.setGrade("二年级");
  12. citation2.setTime(new Date());
  13. System.out.println(citation1);
  14. System.out.println(citation2);

结果,亦未引起另一对象的属性改变,这是因为没有改变两个对象的共有内存(原理图中的new Date()),而是给citation2的time指定了新的引用地址

那么,我们此时改变内存地址200指向的date的值,会发生什么呢

  1. //给张小明颁奖
  2. Citation citation1 = new Citation();
  3. citation1.setName("张小明");
  4. citation1.setGrade("一年级");
  5. citation1.setPrincipal("王校长");
  6. citation1.setTime(new Date());
  7. Thread.sleep(1000);
  8. //给李小萌颁奖
  9. Citation citation2 = (Citation) citation1.clone();
  10. citation2.setName("李小萌");
  11. citation2.setGrade("二年级");
  12. //为citation2的time引用设置新的值
  13. citation2.getTime().setTime(0);
  14. System.out.println(citation1);
  15. System.out.println(citation2);

结果如下:

  1. Citation{name='张小明', grade='一年级', time=Thu Jan 01 08:00:00 CST 1970, principal='王校长'}
  2. Citation{name='李小萌', grade='二年级', time=Thu Jan 01 08:00:00 CST 1970, principal='王校长'}

这就是浅拷贝的弊端,**引用类型共享地址,共享地址的内容发生变化,将会导致所有对象的更改**

正例2(原型模式-深拷贝):

需求**:我们希望克隆出来的副本对象,无论如何更改,都不会影响到原来的对象**

改写对象的克隆方法:

  1. @Getter
  2. @Setter
  3. @ToString
  4. class Citation implements Cloneable{
  5. /**
  6. * 学生姓名
  7. */
  8. String name;
  9. /**
  10. * 学生年级
  11. */
  12. String grade;
  13. /**
  14. * 获奖时间
  15. */
  16. Date time;
  17. /**
  18. * 校长电子签名
  19. */
  20. String principal;
  21. @Override
  22. public Object clone() throws CloneNotSupportedException {
  23. //克隆源对象
  24. Citation clone = (Citation) super.clone();
  25. //将对象中的引用类型也一并克隆,此时得到的timeClone已经和clone.getTime()得到的time地址不一样了
  26. Date timeClone = (Date) clone.getTime().clone();
  27. //将新的引用类型地址设置到对应属性中
  28. clone.setTime(timeClone);
  29. return clone;
  30. }
  31. }

此时调用克隆方法后设置不同时间已然不会影响其他对象:

  1. //给张小明颁奖
  2. Citation citation1 = new Citation();
  3. citation1.setName("张小明");
  4. citation1.setGrade("一年级");
  5. citation1.setPrincipal("王校长");
  6. citation1.setTime(new Date());
  7. Thread.sleep(1000);
  8. //给李小萌颁奖
  9. Citation citation2 = (Citation) citation1.clone();
  10. citation2.setName("李小萌");
  11. citation2.setGrade("二年级");
  12. //为citation2的time引用设置新的值
  13. citation2.getTime().setTime(0);
  14. System.out.println(citation1);
  15. System.out.println(citation2);

结果如下,两个时间不同

  1. Citation{name='张小明', grade='一年级', time=Sat Oct 12 20:37:28 CST 2019, principal='王校长'}
  2. Citation{name='李小萌', grade='二年级', time=Thu Jan 01 08:00:00 CST 1970, principal='王校长'}

那么,当类中存在多个引用类型属性呢?如果引用类型是个含有引用类型成员的对象呢?即对象深度较深时,不断克隆和设置显得臃肿低效

正例3(原型模式-深拷贝)

需求**:我们希望,可以一步到位地深度克隆,即能够同步克隆所有的引用类型成员而不需要重复克隆

利用序列化天然优势进行深度拷贝,让类再实现可序列化接口Serializable

  1. @Getter
  2. @Setter
  3. @ToString
  4. class Citation implements Cloneable,Serializable{
  5. /**
  6. * 学生姓名
  7. */
  8. String name;
  9. /**
  10. * 学生年级
  11. */
  12. String grade;
  13. /**
  14. * 获奖时间
  15. */
  16. Date time;
  17. /**
  18. * 校长电子签名
  19. */
  20. String principal;
  21. @Override
  22. public Object clone() throws CloneNotSupportedException {
  23. //将对象写入内存流
  24. ByteArrayOutputStream out = new ByteArrayOutputStream();
  25. ObjectOutputStream oos = null;
  26. try {
  27. oos = new ObjectOutputStream(out);
  28. oos.writeObject(this); //序列化时,对象的所有属性层级关系会被序列化自动处理
  29. oos.close();
  30. byte[] bytes = out.toByteArray();
  31. //将对象从内存中反序列化出来,生成新的对象
  32. InputStream in = new ByteArrayInputStream(bytes);
  33. ObjectInputStream ois = new ObjectInputStream(in);
  34. Object obj = ois.readObject();
  35. ois.close();
  36. out.close();
  37. return obj;
  38. } catch (IOException e) {
  39. e.printStackTrace();
  40. } catch (ClassNotFoundException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. }

此时调用克隆方法后设置不同时间已然不会影响其他对象:

  1. //给张小明颁奖
  2. Citation citation1 = new Citation();
  3. citation1.setName("张小明");
  4. citation1.setGrade("一年级");
  5. citation1.setPrincipal("王校长");
  6. citation1.setTime(new Date());
  7. Thread.sleep(1000);
  8. //给李小萌颁奖
  9. Citation citation2 = (Citation) citation1.clone();
  10. citation2.setName("李小萌");
  11. citation2.setGrade("二年级");
  12. //为citation2的time引用设置新的值
  13. citation2.getTime().setTime(0);
  14. System.out.println(citation1);
  15. System.out.println(citation2);

结果如下,两个时间不同

  1. Citation{name='张小明', grade='一年级', time=Sat Oct 12 20:37:28 CST 2019, principal='王校长'}
  2. Citation{name='李小萌', grade='二年级', time=Thu Jan 01 08:00:00 CST 1970, principal='王校长'}

需求完美解决!!!

应用场景

  • 对象之间相同或相似,即只是个别的几个属性不同的时候。
  • 对象的创建过程比较麻烦,但复制比较简单的时候。

优点

减少了重复代码,提高代码可读性

如有贻误,还请评论指正