大话设计模式笔记(七)の原型模式
举个栗子
问题描述
要求有一个简历类,必须要有姓名,可以设置性别和年龄,可以设置工作经历,最终需要三份简历。
简单实现
简历类
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 的过程。其中复制便分为浅复制与深复制。
Pass:以上纯属个人理解~~如果发现有错或是心存建议意见等,欢迎大家评论或联系~(# ゚Д゚)~祝大家身体健康学习进步工作顺利生活愉快!
版权归 callmeDevil 所有,如需转载请标注转载来源
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(三):用.NET IoT库
· 【非技术】说说2024年我都干了些啥