经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
大话设计模式笔记(七)の原型模式
来源:cnblogs  作者:callmeDevil  时间:2019/7/15 8:58:02  对本文有异议

举个栗子

问题描述

要求有一个简历类,必须要有姓名,可以设置性别和年龄,可以设置工作经历,最终需要三份简历。

简单实现

简历类

  1. /**
  2. * 简历类
  3. * Created by callmeDevil on 2019/7/13.
  4. */
  5. public class Resume {
  6. private String name;
  7. private String sex;
  8. private String age;
  9. private String timeArea;
  10. private String company;
  11. public Resume (String name) {
  12. this.name = name;
  13. }
  14. /**
  15. * 设置个人信息
  16. * @param sex
  17. * @param age
  18. */
  19. public void setPersonalInfo(String sex, String age){
  20. this.sex = sex;
  21. this.age = age;
  22. }
  23. /**
  24. * 设置工作经历
  25. * @param timeArea
  26. * @param company
  27. */
  28. public void setWorkExperience(String timeArea, String company) {
  29. this.timeArea = timeArea;
  30. this.company = company;
  31. }
  32. /**
  33. * 显示
  34. */
  35. public void display () {
  36. System.out.println(String.format("%s %s %s", name, sex, age));
  37. System.out.println(String.format("工作经历:%s %s", timeArea, company));
  38. }
  39. // 此处省略get、set方法
  40. }

测试

  1. /**
  2. * 测试
  3. * Created by callmeDevil on 2019/7/13.
  4. */
  5. public class Test {
  6. public static void main(String[] args) {
  7. Resume resumeA = new Resume("callmeDevil");
  8. resumeA.setPersonalInfo("男", "24");
  9. resumeA.setWorkExperience("2018-2019", "伟大的航道");
  10. Resume resumeB = new Resume("callmeDevil");
  11. resumeB.setPersonalInfo("男", "24");
  12. resumeB.setWorkExperience("2018-2019", "伟大的航道");
  13. Resume resumeC = new Resume("callmeDevil");
  14. resumeC.setPersonalInfo("男", "24");
  15. resumeC.setWorkExperience("2018-2019", "伟大的航道");
  16. resumeA.display();
  17. resumeB.display();
  18. resumeC.display();
  19. }
  20. }

测试结果

  1. callmeDevil 24
  2. 工作经历:2018-2019 伟大的航道
  3. callmeDevil 24
  4. 工作经历:2018-2019 伟大的航道
  5. callmeDevil 24
  6. 工作经历:2018-2019 伟大的航道

存在的问题

跟手写简历没有差别,三份简历需要三份实例化,如果客户需要二十份简历,那就得实例化二十次。

原型模式

定义

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

UML图

代码实现

  1. /**
  2. * 简历类(实现JDK克隆接口)
  3. * Created by callmeDevil on 2019/7/13.
  4. */
  5. public class Resume implements Cloneable{
  6. private String name;
  7. private String sex;
  8. private String age;
  9. private String timeArea;
  10. private String company;
  11. public Resume (String name) {
  12. this.name = name;
  13. }
  14. /**
  15. * 设置个人信息
  16. * @param sex
  17. * @param age
  18. */
  19. public void setPersonalInfo(String sex, String age){
  20. this.sex = sex;
  21. this.age = age;
  22. }
  23. /**
  24. * 设置工作经历
  25. * @param timeArea
  26. * @param company
  27. */
  28. public void setWorkExperience(String timeArea, String company) {
  29. this.timeArea = timeArea;
  30. this.company = company;
  31. }
  32. /**
  33. * 显示
  34. */
  35. public void display () {
  36. System.out.println(String.format("%s %s %s", name, sex, age));
  37. System.out.println(String.format("工作经历:%s %s", timeArea, company));
  38. }
  39. /**
  40. * 实现克隆方法,可进行自己的克隆逻辑
  41. * @return
  42. * @throws CloneNotSupportedException
  43. */
  44. @Override
  45. protected Object clone() throws CloneNotSupportedException {
  46. return super.clone();
  47. }
  48. // 此处省略get、set方法
  49. }

测试

  1. /**
  2. * 原型模式测试
  3. * Created by callmeDevil on 2019/7/13.
  4. */
  5. public class Test {
  6. public static void main(String[] args) throws CloneNotSupportedException {
  7. Resume resumeA = new Resume("callmeDevil");
  8. resumeA.setPersonalInfo("男", "24");
  9. resumeA.setWorkExperience("2018-2019", "伟大的航道");
  10. // 只需要调用clone方法就可以实现新简历的生成,并且可以修改新简历的细节
  11. Resume resumeB = (Resume) resumeA.clone();
  12. resumeB.setWorkExperience("2019-2020", "新世界");
  13. Resume resumeC = (Resume) resumeA.clone();
  14. resumeC.setPersonalInfo("男", "25");
  15. resumeA.display();
  16. resumeB.display();
  17. resumeC.display();
  18. }
  19. }

测试结果

  1. callmeDevil 24
  2. 工作经历:2018-2019 伟大的航道
  3. callmeDevil 24
  4. 工作经历:2019-2020 新世界
  5. callmeDevil 25
  6. 工作经历:2018-2019 伟大的航道

好处

  • 一般在初始化的信息不发生变化的情况下,克隆是最好的方法。这既隐藏了对象创建的细节,又对性能是大大的提高。
  • 不用重新初始化对象,而是动态的获得对象运行时的状态。

浅复制与深复制

浅复制

在上面这个简历类中,如果字段是值类型(基本数据类型)的,则对该字段直接进行复制;如果是引用类型(String等),则/复制引用/但不/复制引用的对象/;因此,原始对象及其副本引用同一对象。

在此之前,我们先做一个简单的测试

  1. System.out.println("123" == "123");
  2. System.out.println("123".equals("123"));
  3. System.out.println(new String("123") == new String("123"));
  4. System.out.println(new String("123").equals(new String("123")));

相信有点基础的都知道答案吧?就不卖弄了,直接上结果

  1. true
  2. true
  3. false
  4. true

至于结果为什么会这样,网上也许多分析,此处重点在浅复制的讲解,因此不做过多叙述。

带着上面的理解再看下面的内容。在可克隆的简历类例子中,基本数据类型就没什么好测试的,有兴趣的也可以将年龄改成 int 类型;对于其他 String 类型,就拿 company 字段来说,我们新建一个测试类看下是什么结果

  1. public class Test2 {
  2. public static void main(String[] args) throws CloneNotSupportedException {
  3. Resume resumeA = new Resume("callmeDevil");
  4. resumeA.setWorkExperience("0", "伟大的航道");// 直接声明String
  5. Resume resumeB = (Resume) resumeA.clone();
  6. // A 与 B 的公司两种比较
  7. System.out.println(resumeA.getCompany() == resumeB.getCompany());
  8. System.out.println(resumeA.getCompany().equals(resumeB.getCompany()));
  9. resumeA.setWorkExperience("0", new String("伟大的航道"));//new 的方式创建String
  10. Resume resumeC = (Resume) resumeA.clone();
  11. // A 与 C 的公司两种比较
  12. System.out.println(resumeA.getCompany() == resumeC.getCompany());
  13. System.out.println(resumeA.getCompany().equals(resumeC.getCompany()));
  14. }
  15. }

比对第一个“123”的例子,稍微思考下在比对下面结果看是否一致

  1. true
  2. true
  3. true
  4. true

是的,这就是浅复制。不论A简历company直接声明的还是 new 出来的,在clone方法中都只会对 company 字段复制一份引用而已。因此才会出现 “==”“equal()”的结果都是“true”。对于引用类型来说,这个字段所在类实现了clone方法是不够的,它只会对引用类型复制一份引用,那如果连引用类型的字段也要创建一份新的数据,那便是 “深复制”

  • 浅复制就是,被复制的对象的所有变量都含有与原来对象相同的值,而所有其他对象的引用都仍然只想原来的对象。
  • 深复制把引用对象的变量指向复制过的新对象,而不是援用的被引用的对象。

深复制

此处不纠结于如何对上述 String 的深复制。现将简历类进行改造,把“工作经历”抽出成另一个类 WorkExperience

简历类2

  1. /**
  2. * 简历类2(深复制)
  3. * Created by callmeDevil on 2019/7/13.
  4. */
  5. public class Resume2 implements Cloneable {
  6. private String name;
  7. private String sex;
  8. private String age;
  9. // 工作经历
  10. private WorkExperience work;
  11. public Resume2 (String name) {
  12. this.name = name;
  13. this.work = new WorkExperience();
  14. }
  15. private Resume2(WorkExperience work) throws CloneNotSupportedException {
  16. this.work = (WorkExperience) work.clone();
  17. }
  18. /**
  19. * 设置个人信息
  20. * @param sex
  21. * @param age
  22. */
  23. public void setPersonalInfo(String sex, String age){
  24. this.sex = sex;
  25. this.age = age;
  26. }
  27. /**
  28. * 设置工作经历
  29. * @param timeArea
  30. * @param company
  31. */
  32. public void setWorkExperience(String timeArea, String company) {
  33. // 此处赋值给 work 对象
  34. this.work.setWorkDate(timeArea);
  35. this.work.setCompany(company);
  36. }
  37. /**
  38. * 显示
  39. */
  40. public void display () {
  41. System.out.println(String.format("%s %s %s", name, sex, age));
  42. // 此处显示 work 对象的值
  43. System.out.println(String.format("工作经历:%s %s", work.getWorkDate(), work.getCompany()));
  44. }
  45. /**
  46. * 实现克隆方法,可进行自己的克隆逻辑
  47. * @return
  48. * @throws CloneNotSupportedException
  49. */
  50. @Override
  51. protected Object clone() throws CloneNotSupportedException {
  52. // 调用私有的构造方法,让“工作经历”对象克隆完成,然后再给这个“简历”对象
  53. // 相关的字段赋值,最终返回一个深复制的简历对象
  54. Resume2 obj = new Resume2(this.work);
  55. obj.setName(this.name);
  56. obj.setSex(this.sex);
  57. obj.setAge(this.age);
  58. return obj;
  59. }
  60. // 此处省略get、set方法
  61. }

工作经历

  1. /**
  2. * 工作经历
  3. * Created by callmeDevil on 2019/7/13.
  4. */
  5. public class WorkExperience implements Cloneable{
  6. private String workDate;
  7. private String company;
  8. @Override
  9. protected Object clone() throws CloneNotSupportedException {
  10. return super.clone();
  11. }
  12. // 此处省略get、set方法
  13. }

测试类

  1. /**
  2. * 深复制测试
  3. * Created by callmeDevil on 2019/7/13.
  4. */
  5. public class TestDeepClone {
  6. public static void main(String[] args) throws CloneNotSupportedException {
  7. Resume2 resumeA = new Resume2("callmeDevil");
  8. resumeA.setPersonalInfo("男", "24");
  9. resumeA.setWorkExperience("2018-2019", "伟大的航道");
  10. Resume2 resumeB = (Resume2) resumeA.clone();
  11. resumeB.setWorkExperience("2019-2020", "新世界");
  12. Resume2 resumeC = (Resume2) resumeA.clone();
  13. resumeC.setWorkExperience("2020-XXXX", "木叶忍者村");
  14. resumeA.display();
  15. resumeB.display();
  16. resumeC.display();
  17. }
  18. }

测试结果

  1. callmeDevil 24
  2. 工作经历:2018-2019 伟大的航道
  3. callmeDevil 24
  4. 工作经历:2019-2020 新世界
  5. callmeDevil 24
  6. 工作经历:2020-XXXX 木叶忍者村

可以看到,每次克隆都能将简历类中的工作经历类一同新建,而不是单纯的同个对象进行改变内容。如果是浅复制的实现,那么在相同测试类中会出现什么结果呢?应该能猜到吧,那就是三次输出都是一样的。
对于工作经历类浅复制实现本文就不描述了,有兴趣的可以自行测试,很简单,需要修改的地方有这么两处:

  • 简历类实现的克隆方法中直接调用 super.clone() 而不需要重写。
  • 取消工作经历类实现克隆接口及其方法。

只需要更改这两处,在相同的测试类中就能看到以下结果

  1. callmeDevil 24
  2. 工作经历:2020-XXXX 木叶忍者村
  3. callmeDevil 24
  4. 工作经历:2020-XXXX 木叶忍者村
  5. callmeDevil 24
  6. 工作经历:2020-XXXX 木叶忍者村

看到此处就不需要解释了吧,每次克隆只是新实例化了“简历”,但三个“简历”中的“工作经历”都是同一个,每次 set值 的时候都必将影响到所有对象,所以输出的工作经历都是相同的。这也同时说明了实现深复制的必要条件

  • 需要实现深复制的引用类型字段的类(比如上文中的工作经历)必须实现 Cloneable 接口
  • 该字段的所属类(比如上文中的简历)实现的克隆方法中必须对该字段进行克隆与赋值

做到以上两点,即可将原本浅复制的功能转变为深复制

总结

原型模式无非就是对指定创建的原型实例进行复制,再对新对象另做操作,省去重新 new 的过程。其中复制便分为浅复制深复制

原文链接:http://www.cnblogs.com/call-me-devil/p/11180403.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号