一、原型模式定义
原型模式采用复制原型对象的方法来创建实例。使用原型模式创建出来的对象,具有与原型一样的数据结构,也拥有与原型一样的值。
核心思想:对象创建的动作发自原型对象自身(clone()),而不是由外部new出来。
二、原型模式的特点
- 由原型对象自身创建目标对象:即"对象创建"这一动作来自原型对象本身的
clone()方法。 - 目标对象是原型的一个克隆:通过原型模式创建的对象,不仅与原型有相同的结构,还有相同的值。
- 根据克隆深度的不同,分浅度克隆与深度克隆(见第四节)。
三、基础代码实现
Object类自带protected Object clone(),但只有实现了Cloneable标记接口的类,调用clone()才不会抛CloneNotSupportedException。
// 必须实现 Cloneable 接口才能克隆publicclassPersonimplementsCloneable{privateStringname;privateStringage;privateStringsex;publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.name=name;}publicStringgetAge(){returnage;}publicvoidsetAge(Stringage){this.age=age;}publicStringgetSex(){returnsex;}publicvoidsetSex(Stringsex){this.sex=sex;}// 克隆方法:重写 Object.clone(),提升为 public@OverridepublicPersonclone(){try{return(Person)super.clone();// 默认是【浅克隆】}catch(CloneNotSupportedExceptione){e.printStackTrace();returnnull;}}}publicclassMainClass{publicstaticvoidmain(String[]args){Personperson1=newPerson();person1.setName("李白");person1.setAge("34");person1.setSex("男");// 克隆对象(堆里生成了一份新对象)Personperson2=person1.clone();System.out.println(person2.getName());// 李白(与原型数据一致)}}⚠️
super.clone()默认执行的是浅克隆(见下节)。这里的字段都是String(不可变)和基本类型,浅克隆看不出副作用。
四、浅克隆 vs 深克隆(核心)
4.1 一个关键澄清
super.clone()的本质是:逐字段复制。
- 基本类型(int、double…):直接复制值,互不干扰;
- 引用类型(对象、数组…):只复制引用地址,新旧对象指向同一个引用对象。
原笔记说"数值对象会生成全新一份"并不准确——
String是引用类型,只是因为它不可变,即使浅克隆共享了引用,重新赋值p.name="x"也只是让p指向新字符串,不会改动原对象,所以"看起来"没副作用。要真正暴露浅克隆问题,必须用可变引用对象。
4.2 浅克隆的副作用(用可变对象演示)
给Person增加一个可变的引用属性Address:
publicclassAddressimplementsCloneable{privateStringcity;publicAddress(Stringcity){this.city=city;}publicStringgetCity(){returncity;}publicvoidsetCity(Stringcity){this.city=city;}@OverridepublicAddressclone(){try{return(Address)super.clone();}catch(CloneNotSupportedExceptione){returnnull;}}}publicclassPersonimplementsCloneable{privateStringname;privateAddressaddress;// 可变引用对象// 浅克隆:address 引用被复制,p1、p2 指向同一个 Address@OverridepublicPersonclone(){try{return(Person)super.clone();}catch(CloneNotSupportedExceptione){returnnull;}}}publicclassMainClass{publicstaticvoidmain(String[]args){Personp1=newPerson("李白",newAddress("长安"));Personp2=p1.clone();// 浅克隆p2.getAddress().setCity("洛阳");// 改 p2 的地址System.out.println(p1.getAddress().getCity());// 输出"洛阳"!p1 被牵连}}结论:浅克隆下,p1和p2共享同一个Address,改一个另一个跟着变——这通常不是我们想要的。
4.3 深克隆的修法
深克隆 = 在clone()里连引用对象也一起克隆:
publicclassPersonimplementsCloneable{privateStringname;privateAddressaddress;@OverridepublicPersonclone(){try{Personp=(Person)super.clone();p.address=this.address.clone();// 引用对象也克隆一份returnp;}catch(CloneNotSupportedExceptione){returnnull;}}}此时p2.getAddress().setCity("洛阳")不会影响p1,二者完全独立。
4.4 深克隆的另一种方式:序列化
当对象层级很深时,逐层clone()很繁琐。可让类实现Serializable,通过序列化成字节流再反序列化得到完全独立的新对象(要求所有引用对象都可序列化):
// 伪代码思路:把对象写进 ByteArrayOutputStream,再读回 → 全新深拷贝面试常问:深克隆两种实现——逐层 clone()或序列化。前者直观但层次深时繁琐,后者"一行深拷贝"但要求全链路可序列化。
五、角色与职责
| 角色 | 职责 |
|---|---|
| 抽象原型(Prototype) | 声明克隆自身的接口。Java 中即Cloneable标记接口 +clone()方法 |
| 具体原型(Concrete Prototype) | 实现clone(),返回自身的一个副本 |
| 客户(Client) | 通过调用原型的clone()创建新对象,而不使用new |
六、应用场景
- 创建对象时,希望新对象既继承基类结构,又继承原型的数据:直接
clone比从头new再逐个set更高效。 - 希望修改目标对象不影响既有原型:配合深克隆,克隆体与原型完全隔离。
- 隐藏克隆细节:对象本身的克隆往往涉及类内部数据细节,用原型模式把这些细节封装在
clone()内,调用方无感知。 - 对象创建成本高 / 构造复杂:如从数据库或网络加载后缓存为原型,后续直接复制,省去昂贵初始化。
七、总结
- 原型模式 =复制现有对象来创建新对象,而非
new。 - 浅克隆:复制基本类型值 + 复制引用地址(引用对象共享);深克隆:引用对象也一并克隆(完全独立)。
String不可变,单用String字段无法体现浅克隆问题,必须用可变引用对象演示。- 记忆:浅克隆共享引用、深克隆独立副本;层次深时用序列化做深克隆更省事。
一句话总结:原型模式用
clone()拷贝代替new,省去重复初始化;用之前先想清楚——你的对象里有没有可变引用?有,就老老实实做深克隆,否则改一个坑一片。