原型模式
1、原型模式介绍
原型模式是一种创建型设计模式,它允许通过复制现有对象来生成新对象,而无需编写从头开始创建新对象的代码。
1.1 具体介绍
在原型模式中,我们首先创建一个原型对象,然后通过复制该对象来创建新的实例,新的对象实例不需要知道任何创建的细节,只需要知道如何复制即可得到一个与原型一模一样的新对象。这种方法比直接创建对象要快,因为在复制过程中不需要执行复杂的初始化操作。原型模式还可以减少代码重复,因为我们可以通过复制现有的对象来避免多次编写相同的创建代码。
在实现原型模式时,我们通常使用一个原型管理器来存储原型对象。这个管理器允许我们在需要时获取原型对象的副本,而不是直接创建新对象。
原型模式在许多场景中都非常有用,例如在需要创建大量相似对象的情况下。它还可以用于避免复杂的初始化操作或构造函数,并且可以使代码更加灵活和可扩展。
1.2 原型模式角色
原型模式通常包括两个角色:原型类和具体原型类。
- 原型类是一个抽象的类或接口,声明了用于复制自己的方法。
- 具体原型类是具体的实现类,在实现父类(或接口)中定义的复制方法时,需要注意实现深拷贝和浅拷贝,以确保复制出来的对象完全符合预期。
2、具体例子
2.1 违反原型模式例子
复印简历的例子,对于我们程序员来说,简历也是一个很重要的东西。
Resume
类
/** * @author Shier * CreateTime 2023/4/21 22:03 * 简历类 */ public class Resume { private String name; private String sex; private String age; private String company; private String workTime; public Resume(String name) { this.name = name; } /** * 设置个人信息 */ public void setPersonalInfo(String sex, String age) { this.sex = sex; this.age = age; } /** * 设置工作经历 */ public void setWorkExperience(String company, String workTime) { this.company = company; this.workTime = workTime; } /** * 展示简历 */ public void showResume() { System.out.println("姓名:" + this.name + "\t年龄" + this.age + "\t性别" + this.sex); System.out.println("工作经历:" + this.company + "\t时间:" + this.workTime); } }
测试类:
/** * @author Shier * CreateTime 2023/4/21 22:08 */ public class ResumeTest1 { public static void main(String[] args) { Resume resume1 = new Resume("Shier"); resume1.setPersonalInfo("男", "19"); resume1.setWorkExperience("鱼皮科技", "2023-04~05"); Resume resume2 = new Resume("Shier"); resume2.setPersonalInfo("男", "19"); resume2.setWorkExperience("鱼皮科技", "2023-04~05"); Resume resume3 = new Resume("Shier"); resume3.setPersonalInfo("男", "19"); resume3.setWorkExperience("鱼皮科技", "2023-04~05"); resume1.showResume(); resume2.showResume(); resume3.showResume(); } }
最终结果显示:
这样就可以得到三分简历,但是你有没有想过,如果我要准备一百分呢?是不是就要去new 一百个Resume类,来创建新的对象。这样做虽然是可以,但是重复的代码也太多了吧,做重复的工作,而且消耗的内存也多。
2.2 使用原型模式改进
那个原型抽象类Prototype是用不着的,因为克隆实在是太常用了,所以Java提供了Cloneable接口,其中就是唯一的一个方法clone(),这样你就只需要实现这个接口就可以完成原型模式了
改进后的Resume类的UML类图如下
具体代码如下:
/** * @author Shier * CreateTime 2023/4/21 22:03 * 简历类 */ public class Resume implements Cloneable { private String name; private String sex; private String age; private String company; private String workTime; public Resume(String name) { this.name = name; } /** * 设置个人信息 */ public void setPersonalInfo(String sex, String age) { this.sex = sex; this.age = age; } /** * 设置工作经历 */ public void setWorkExperience(String company, String workTime) { this.company = company; this.workTime = workTime; } /** * 展示简历 */ public void showResume() { System.out.println("姓名:" + this.name + "\t年龄" + this.age + "\t性别" + this.sex); System.out.println("工作经历:" + this.company + "\t时间:" + this.workTime); } /** * 实现clone方法 */ public Resume clone() { Resume object = null; // 使用克隆对象进行克隆内容 try { object = (Resume) super.clone(); } catch (CloneNotSupportedException e) { System.out.println("克隆异常了"); throw new RuntimeException(e); } return object; } }
测试类:
/** * @author Shier * CreateTime 2023/4/21 22:08 */ public class ResumeTest1 { public static void main(String[] args) { Resume resume1 = new Resume("Shier"); resume1.setPersonalInfo("男", "19"); resume1.setWorkExperience("鱼皮科技1", "2023-04~05"); // 使用resume1进行调用clone对象 Resume resume2 = resume1.clone(); resume2.setWorkExperience("鱼皮科技2", "2023-04~05"); Resume resume3 = resume1.clone(); resume3.setWorkExperience("鱼皮科技3", "2023-04~05"); resume1.showResume(); resume2.showResume(); resume3.showResume(); } }
结果同上
3 浅拷贝与深拷贝
现在’简历’对象里的数据都是String型的,而String是一种拥有值类型特点的特殊引用类型,super.clone()方法是这样,如果字段是值类型的,则对该字段执行逐位复制,如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其副本引用同一对象。什么意思呢?就是说如果你的’简历’类当中有对象引用,那么引用的对象数据是不会被克隆过来的。
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。但我们可能更需要这样的一种需求,把要复制的对象所引用的对象都复制一遍
3.1 浅拷贝例子
/** * @author Shier * CreateTime 2023/4/21 22:32 * 工作经历类 */ public class WorkExperience { private String company; private String workTime; public String getCompany() { return company; } public void setCompany(String company) { this.company = company; } public String getWorkTime() { return workTime; } public void setWorkTime(String workTime) { this.workTime = workTime; } }
/** * @author Shier * CreateTime 2023/4/21 22:03 * 简历类 */ public class Resume implements Cloneable { private String name; private String sex; private String age; private WorkExperience workExperience; public Resume(String name) { this.name = name; this.workExperience = new WorkExperience(); } /** * 设置个人信息 */ public void setPersonalInfo(String sex, String age) { this.sex = sex; this.age = age; } /** * 设置工作经历 */ public void setWorkExperience(String company, String workTime) { this.workExperience.setCompany(company); this.workExperience.setWorkTime(workTime); } /** * 展示简历 */ public void showResume() { System.out.println("姓名:" + this.name + "\t年龄" + this.age + "\t性别" + this.sex); System.out.println("工作经历:" + this.workExperience.getCompany() + "\t时间:" + this.workExperience.getWorkTime()); } /** * 实现clone方法 */ public Resume clone() { Resume object = null; // 使用克隆对象进行克隆内容 try { object = (Resume) super.clone(); } catch (CloneNotSupportedException e) { System.out.println("克隆异常了"); throw new RuntimeException(e); } return object; } }
测试类:
/** * @author Shier * CreateTime 2023/4/21 22:08 */ public class ResumeTest1 { public static void main(String[] args) { Resume resume1 = new Resume("Shier"); resume1.setPersonalInfo("男", "19"); resume1.setWorkExperience("鱼皮科技1", "2023-04~05"); Resume resume2 = resume1.clone(); resume2.setWorkExperience("鱼皮科技2", "2023-04~05"); Resume resume3 = resume1.clone(); resume3.setWorkExperience("鱼皮科技3", "2023-04~05"); resume1.showResume(); resume2.showResume(); resume3.showResume(); } }
结果显示:
这个结果和我们的预期的不一样,第三个把前面两个都覆盖掉了。
由于它是浅表拷贝,所以对于值类型,没什么问题,对引用类型,就只是复制了引用,对引用的对象还是指向了原来的对象,所以就会出现我给resume1、resume2、resume3三个引用设置’工作经历’,但却同时看到三个引用都是最后一次设置,因为三个引用都指向了同一个对象。
3.2 深拷贝例子
3.2.1 深拷贝介绍
深拷贝把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。
深拷贝是指创建一个新对象,并将原始对象中的所有非静态字段及其关联对象的值复制到新对象中。如果字段是基本数据类型,则拷贝它们的值;如果字段是引用类型,则递归地拷贝它们所指向的对象,直到所有引用对象都被拷贝为止。因此,原始对象和副本对象将不共享任何对象。
3.2.2 改进程序
再次修改上面的程序,UML类图如下:
修改工作经历类:
package com.shier.sourcepattern; /** * @author Shier * CreateTime 2023/4/21 22:32 * 工作经历类 */ public class WorkExperience implements Cloneable{ private String company; private String workTime; public String getCompany() { return company; } public void setCompany(String company) { this.company = company; } public String getWorkTime() { return workTime; } public void setWorkTime(String workTime) { this.workTime = workTime; } /** * 实现clone方法 */ public WorkExperience clone() { WorkExperience object = null; // 使用克隆对象进行克隆内容 try { object = (WorkExperience) super.clone(); } catch (CloneNotSupportedException e) { System.out.println("克隆异常了"); throw new RuntimeException(e); } return object; } }
修改简历类:
/** * 实现clone方法 */ public Resume clone() { Resume object = null; // 使用克隆对象进行克隆内容 try { object = (Resume) super.clone(); // 进行深拷贝 this.workExperience = this.workExperience.clone(); } catch (CloneNotSupportedException e) { System.out.println("克隆异常了"); throw new RuntimeException(e); } return object; }
测试的代码不用改变
运行结果显示:
看到这个三个都是不同的,即达到了目的。
4、总结
原型模式通过使用原型管理器来存储原型对象,并在需要时获取原型对象的副本,以避免多次创建相同的对象。
原型模式优点:
可以在不编写创建代码的情况下创建新对象。
可以减少代码重复,因为我们可以通过拷贝现有对象来避免多次编写相同的创建代码。
可以减少初始化操作或构造函数,并使代码更加灵活和可扩展。
原型模式缺点:
如果拷贝操作很复杂,可能会导致性能问题。
如果对象有循环依赖关系,则需要特殊处理。
原型模式应用场景:
创建成本较大的对象:某些对象的创建过程需要耗费大量时间和资源,例如数据库连接对象、网络连接对象等。在这种情况下,使用原型模式可以避免重复创建相同的对象,从而提高系统的性能和效率。
大量相似对象的创建:某些对象可能存在大量相似的情况,例如在图形界面中创建图形对象时,往往会存在大量相似的图形对象,只是具体属性不同。在这种情况下,使用原型模式可以通过复制现有对象来创建新对象,避免从头开始创建新对象的代码。
对象的复杂组合:某些对象的创建需要组合多个对象,例如在设计图形界面中的窗口对象时,需要组合多个控件对象。在这种情况下,使用原型模式可以通过复制现有对象来创建新对象,并在需要的时候修改其中某些组件,从而简化对象的创建过程。
4.2 深浅拷贝
浅拷贝的优点:
相对于深拷贝,浅拷贝的效率更高,因为它只是拷贝引用,而不是递归地拷贝所有相关对象。
浅拷贝对于某些对象来说是合适的,例如拷贝一些不包含引用类型字段的简单对象。
浅拷贝的缺点:
如果原始对象中的引用类型字段被修改了,那么拷贝后的对象也会被修改,这可能会导致意外的副作用。
深拷贝的优点:
深拷贝可以创建完全独立的对象,与原始对象不共享任何引用对象。
深拷贝可以避免意外的副作用。
深拷贝的缺点:
相对于浅拷贝,深拷贝的效率更低,因为它需要递归地拷贝所有相关对象。
深拷贝可能会导致循环引用问题,需要特殊处理。
建新对象的代码。
3. 对象的复杂组合:某些对象的创建需要组合多个对象,例如在设计图形界面中的窗口对象时,需要组合多个控件对象。在这种情况下,使用原型模式可以通过复制现有对象来创建新对象,并在需要的时候修改其中某些组件,从而简化对象的创建过程。
4.2 深浅拷贝
浅拷贝的优点:
- 相对于深拷贝,浅拷贝的效率更高,因为它只是拷贝引用,而不是递归地拷贝所有相关对象。
浅拷贝对于某些对象来说是合适的,例如拷贝一些不包含引用类型字段的简单对象。
浅拷贝的缺点:
如果原始对象中的引用类型字段被修改了,那么拷贝后的对象也会被修改,这可能会导致意外的副作用。
深拷贝的优点:
深拷贝可以创建完全独立的对象,与原始对象不共享任何引用对象。
深拷贝可以避免意外的副作用。
深拷贝的缺点:
相对于浅拷贝,深拷贝的效率更低,因为它需要递归地拷贝所有相关对象。
深拷贝可能会导致循环引用问题,需要特殊处理。