【设计模式与体系结构】创建型模式-原型模式
简介
原型模式(Prototype Pattern)指的是用一个已经创建的对象作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
原型模式的角色
- 抽象原型类:规定具体原型对象必须实现的
方法 - 具体原型类:实现抽象原型类的
方法,它是可被复制的对象 - 访问类:使用具体原型类的
方法来复制新的对象
原型模式的类型
- 浅克隆:创建一个新对象,新对象的属性和原来的对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,而不是指向原来对象的地址
原型模式的使用场景
- 对象的创建过程十分复杂,可以使用原型模式快速创建对象
- 性能和安全要求比较高
正文
浅克隆
世上第一只克隆羊 Dolly 人尽皆知,下面就以此写一份代码:
母羊的代码:
public class Sheep implements Cloneable { private String gene;//非基本类型,因此浅克隆得到的是引用类型 @NonNull @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } public String getGene() { return gene; } public void setGene(String gene) { this.gene = gene; } }
克隆出 Dolly 的代码:
private void shallowClone() { Sheep mother = new Sheep(); mother.setGene("456"); try { Sheep Dolly = (Sheep) mother.clone(); Log.i("Clone", "mother == Dolly? " + (mother == Dolly)); Log.i("Clone", (mother.getGene() == Dolly.getGene()) + ""); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } }
运行截图如下:
由上述代码不难看出,实现
对于引用类型的浅克隆,我们有一个共识:修改克隆的对象的引用类型属性后,被克隆对象的对应属性也一同修改(准确点说是修改的同一个属性)。不妨对 Dolly 的案例进行测试:
private void shallowClone() { Sheep mother = new Sheep(); mother.setGene("456"); try { Sheep Dolly = (Sheep) mother.clone(); Log.i("Clone", "mother == Dolly? " + (mother == Dolly)); Log.i("Clone", (mother.getGene() == Dolly.getGene()) + ""); Dolly.setGene("123"); Log.i("Clone", (mother.getGene() == Dolly.getGene()) + ""); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } }
运行截图如下:
既然对于非基本类型的浅克隆都是引用类型,为什么会出现修改 Dolly 的 String 类型的 gene 属性时,母羊没有同时修改呢?
答:String 类型虽然属于非基本类型,但是 Java 存在常量池机制,不同的 String 类型字符串都会存储在常量池中,因此不同对象的 gene 指向了不同的地址。
因此我们需要对 String 这个非基本类型特别看待,那么我们不妨用其他非基本类型的数据对“引用类型”这个性质进行验证。
深克隆
假设同学 A 和同学 B 是同班同学。每个学生都有姓名、性别等属性,这些属性都是非基本类型 String,也有年龄(int)、身高(float)等属性,这些属性都是基本类型。
对每个同学而言,都具备姓名、性别、年龄和身高等属性,且是个人特有的。假设我们想由同学 A 对象克隆出同学 B 对象,那么非基本类型再使用浅克隆就不合适了。因为两位同学的名字、性别等明显是不共享的,因此不可以使用引用类型,而是要用拷贝类型,所以要使用深克隆。
对于深克隆的参考文章为:https://www.cnblogs.com/gollong/p/9668699.html
对于引用类型,我们将其也实现 Cloneable 接口,这样子就可以使得引用类型的属性也变为拷贝类型
实现方式一:clone()函数的嵌套调用
定义一个学生类 Student.java
学生类
public class Student implements Cloneable { private Information information; private int schoolId; public Student(int schoolId, int age, float height) { this.schoolId = schoolId; this.information = new Information(age, height); } public Information getInformation() { return information; } public void setInformation(Information information) { this.information = information; } public int getSchoolId() { return schoolId; } public void setSchoolId(int schoolId) { this.schoolId = schoolId; } @Override public Student clone() throws CloneNotSupportedException { Student student = (Student) super.clone(); student.information = (Information) information.clone(); return student; } }
定义一个信息类 Information.java
信息类
public class Information implements Cloneable { private int age; private float height; public Information(int age, float height) { this.age = age; this.height = height; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public float getHeight() { return height; } public void setHeight(float height) { this.height = height; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "age: " + getAge() + " height: " + getHeight(); } }
测试深克隆模式实现方式一
clone() 方法的嵌套调用测试
private void testDeepClone() { Student stu1 = new Student(1, 15, 165.5f); try { Student stu2 = stu1.clone(); System.out.println("修改前:"); System.out.println(stu1.toString() + " " + stu2.toString() + " 相等与否?" + stu1.toString().equals(stu2.toString())); System.out.println("stu1的信息:" + stu1.getSchoolId() + " " + stu1.getInformation()); System.out.println("stu2的信息:" + stu2.getSchoolId() + " " + stu2.getInformation()); System.out.println("学校:" + (stu1.getSchoolId() == stu2.getSchoolId())); System.out.println("信息:" + (stu1.getInformation() == stu2.getInformation())); System.out.println("修改后:"); stu1.setSchoolId(2); stu2.getInformation().setAge(18); stu2.getInformation().setHeight(175.3f); System.out.println(stu1.toString() + " " + stu2.toString() + " 相等与否?" + stu1.toString().equals(stu2.toString())); System.out.println("stu1的信息:" + stu1.getSchoolId() + " " + stu1.getInformation()); System.out.println("stu2的信息:" + stu2.getSchoolId() + " " + stu2.getInformation()); System.out.println("学校:" + (stu1.getSchoolId() == stu2.getSchoolId())); System.out.println("信息:" + (stu1.getInformation() == stu2.getInformation())); } catch (CloneNotSupportedException e) { e.printStackTrace(); } }
运行截图如下:
实现方式二:序列化
clone() 方法的嵌套调用,已经可以实现深克隆的需求。但是当所要克隆的类嵌套深度较大,或者数据结构较为复杂的情况下,方式一就略显复杂,并且不符合开闭原则。而序列化只需要为每一个类实现一个 Serializable 接口,最后通过序列化和反序列化即可实现深克隆。
定义一个学生类 Student.java
学生类
public class Student implements Serializable { private Information information; private int schoolId; public Student(int schoolId, int age, float height) { this.schoolId = schoolId; this.information = new Information(age, height); } public Information getInformation() { return information; } public void setInformation(Information information) { this.information = information; } public int getSchoolId() { return schoolId; } public void setSchoolId(int schoolId) { this.schoolId = schoolId; } }
定义一个信息类 Information.java
信息类
public class Information implements Serializable { private int age; private float height; public Information(int age, float height) { this.age = age; this.height = height; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public float getHeight() { return height; } public void setHeight(float height) { this.height = height; } @Override public String toString() { return "age: " + getAge() + " height: " + getHeight(); } }
测试深克隆模式实现方式二
序列化测试
private void testDeepClone() { Student stu1 = new Student(1, 15, 165.5f); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos; try { oos = new ObjectOutputStream(baos); oos.writeObject(stu1); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); Student stu2 = (Student) ois.readObject(); System.out.println("修改前:"); System.out.println(stu1.toString() + " " + stu2.toString() + " 相等与否?" + stu1.toString().equals(stu2.toString())); System.out.println("stu1的信息:" + stu1.getSchoolId() + " " + stu1.getInformation()); System.out.println("stu2的信息:" + stu2.getSchoolId() + " " + stu2.getInformation()); System.out.println("学校:" + (stu1.getSchoolId() == stu2.getSchoolId())); System.out.println("信息:" + (stu1.getInformation() == stu2.getInformation())); System.out.println("修改后:"); stu1.setSchoolId(2); stu2.getInformation().setAge(18); stu2.getInformation().setHeight(175.3f); System.out.println(stu1.toString() + " " + stu2.toString() + " 相等与否?" + stu1.toString().equals(stu2.toString())); System.out.println("stu1的信息:" + stu1.getSchoolId() + " " + stu1.getInformation()); System.out.println("stu2的信息:" + stu2.getSchoolId() + " " + stu2.getInformation()); System.out.println("学校:" + (stu1.getSchoolId() == stu2.getSchoolId())); System.out.println("信息:" + (stu1.getInformation() == stu2.getInformation())); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
运行截图如下:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 【.NET】调用本地 Deepseek 模型