大话设计模式笔记(七)の原型模式

举个栗子

问题描述

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

简单实现

简历类

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

测试

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

测试结果

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

存在的问题

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

原型模式

定义

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

UML图

代码实现

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

测试

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

测试结果

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

好处

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

浅复制与深复制

浅复制

在上面这个简历类中,如果字段是值类型(基本数据类型)的,则对该字段直接进行复制;如果是引用类型(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")));
5.  

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

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

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

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

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

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

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

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

工作经历

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

测试类

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

测试结果

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

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

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

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

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

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

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

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

总结

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

posted @   callmeDevil  阅读(504)  评论(0编辑  收藏  举报
编辑推荐:
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
阅读排行:
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(三):用.NET IoT库
· 【非技术】说说2024年我都干了些啥
点击右上角即可分享
微信分享提示