本文最后更新于 2025年7月25日 凌晨
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象,在这里原型实例指定了要创建的对象的种类,用这种方式创建对象非常高效,根本无需知道对象的创建细节
角色
1 抽象原型类 : 规定了具体原型对象必须实现的接口
2 具体原型类 : 实现抽象原型类的clone()方法,它是可被复制的对象
3 访问类 : 使用具体原型类中的clone()方法来复制新的对象
优点
效率高,直接克隆,避免了重新执行构造过程的步骤。克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆对象的属性值完全和原型对象相同,并且克隆的新对象的改变不会影响原型对象
缺点
原型模式通过clone()方法创建对象副本,因此设计一个安全且高效的 clone 方法往往较为复杂。
特别是当原始对象中存在 持久性资源连接 (如数据库连接、文件句柄、Socket 等)时,直接复制这些资源引用会导致克隆对象出现资源冲突或不可用的问题。为了确保克隆后的对象行为正确,开发者需要特别处理这些资源的重建或剥离过程,这无疑加大了 clone 方法的设计难度和出错风险。
实现原型模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| public class Resume implements Cloneable{ private String name ; private String position ; private int salary ;
public String getName(){ return name ; } public void setName(String name){ this.name = name ; } public String getPosition(){ return position ; } public void setPosition(String position){ this.position = position; } public int getSalary() { return salary ; } public void setSalary(int salary){ this.salary = salary ; } @Override protected Resume clone(){ Resume resume = null ; try { resume = (Resume)super.clone(); } catch(CloneNotSupportedException e){ e.printStackTrace(); } return resume ; }
@Override public String toString(){ return "Resume{" + "name=\\'" + name + "\\'," + "position= \\'" + position + "\\'," + "salary=\\'" + salary + "\\'}"; }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class PostResume{ public static void main(String[] args){ Resume resume1 = new Resume(); resume1 = new Resume(); resume1.setName("Codfish"); resume1.setPosition("测试工程师"); System.out.println(resume1); Resume resume2 = resume1.clone(); resume2.setName("catfish"); }
}
|
拷贝问题
当我们进行对象拷贝时,拷贝的究竟是什么?
在现代编程语言中,对象的内部属性可以分为两类:
- 值类型(value type)属性 :例如整数、布尔、浮点数等,实际存储的是数据本身。
- 引用类型(reference type)属性 :例如数组、自定义类、集合等,实际存储的是指向对象在堆内存中的地址(指针)。
内存表示模型(简化说明)
1 2 3 4 5 6
| java 复制编辑 Object ├── value_field └── reference_field
|
浅拷贝与引用共享
在进行浅拷贝(即默认的 Object.clone() 或简单赋值)时:
- 值类型字段 :会被逐字节复制到新对象中,两个对象互不影响。
- 引用类型字段 :复制的是“引用地址”,即两个对象共享同一个堆内对象。这样就会导致克隆对象与原对象 指向相同的子对象实例 ,从而引发 状态共享问题 。
例如:
1 2 3 4 5 6 7 8 9 10
| java 复制编辑 Resume resume1 = new Resume(); resume1.setEducation(new Education("MIT", "2020-2024"));
Resume resume2 = resume1.clone(); resume2.getEducation().setSchoolName("Harvard");
|
深拷贝与对象隔离
为了避免这种引用共享导致的副作用,必须在 clone() 方法中 显式地对引用类型字段进行深拷贝 ,即:
- 对每个引用类型字段调用其
clone() 方法(前提是该字段类型也实现了 Cloneable)。
- 并将新生成的子对象重新赋值给克隆对象中的字段,确保两个对象的引用指向 完全不同的实例 。
处理深拷贝情况
如当我们向resume 中添加了一个自定义类(本身会创建一个引用对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class Education implements Cloneable { private String schoolName ; private String time ; public String getSchoolName(){ return schoolName ; } public void setSchoolName(String schoolName){ this.schoolName = schoolName ; } public String getTime(){ return time ; } public void setTime(String time ){ this.time = time ; }
@Override protected Education clone(){ try { return (Education)super.clone(); }catch (CloneNotSupportedException e){ e.printStackTrace(); } return null ; } @Override public String toString(){ return "Education{" + "schoolName='" + schoolName + "\\'" + "time=\\'" + time + "\\'" + "}"; } }
|
需要对clone方法进行对应的修改,将resume中的Education 引用对象 与新创建的Education 对象进行绑定
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Override protected Resume clone(){ Resume resume = null ; try { resume = (Resume) super.clone(); if(this.education != null){ resume.setEducation(this.education.clone()); } }catch (CloneNotSupportedException e){ e.printStackTrace(); } return resume ; }
|
总结
原型模式是一种通过复制已有实例(原型)来创建新对象的创建型设计模式,它跳过了复杂的构造过程,适用于对象创建成本高或结构复杂的场景。
其核心在于调用 clone() 方法进行 对象内存层面的复制 ,相比于 new 操作,它能保留原型对象的属性状态并快速生成副本,从而提升效率。