原型模式(Prototype Pattern),使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。原型模式会要求对象实现一个可以“克隆”自身的接口,这样就可以通过拷贝或者是克隆一个实例对象本身,来创建一个新的实例。如果把这个方法定义在接口上,看起来就像是通过接口来创建了新的接口对象。
在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,因为原型模式是基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
原型模式的组成为:
| 组成 | 作用 | |
|---|---|---|
| Prototype | 声明一个克隆自身的接口,用来约束想要克隆自己的类,要求它们都要实现这里定义的克隆方法。 | |
| ConcretePrototype) | 实现Prototype接口的类,这些类真正实现了克隆自身的功能。 |
原型模式的优点
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作
原型模式的缺点
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当
深克隆和浅克隆
- 浅克隆
创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
- 深克隆
创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址
public class CloneTest {private static class RefClass implements Cloneable {private String filed;private RefClass(String filed) {this.filed = filed;}@Overrideprotected RefClass clone() throws CloneNotSupportedException {return ((RefClass) super.clone());}}private static class TestClass implements Cloneable {private RefClass filed;private TestClass(RefClass filed) {this.filed = filed;}@Overrideprotected TestClass clone() throws CloneNotSupportedException {return ((TestClass) super.clone());}private TestClass deepClone() throws CloneNotSupportedException {TestClass clone = (TestClass) super.clone();// 引用类型也要克隆clone.filed = filed.clone();return clone;}}public static void main(String[] args) throws CloneNotSupportedException {RefClass refClass = new RefClass("引用类型");TestClass testClass = new TestClass(refClass);TestClass clone = testClass.clone();System.out.println("浅克隆的结果:\n");System.out.println("testClass == clone: " + (testClass == clone));System.out.println("\ntestClass.filed == clone.filed: " + (testClass.filed == clone.filed));clone = testClass.deepClone();System.out.println("\n深克隆的结果:\n");System.out.println("testClass == clone: " + (testClass == clone));System.out.println("\ntestClass.filed == clone.filed: " + (testClass.filed == clone.filed));}}
运行结果如下:
:::success
浅克隆的结果:
testClass == clone: false
testClass.filed == clone.filed: true
深克隆的结果:
testClass == clone: false
testClass.filed == clone.filed: false
:::
由此可见,浅克隆得到的新对象,两者使用的是同一个对象,当某一个对象发生改变时,另一个也会发生改变。因为对于成员变量为引用类型的数据来说,clone只是创建出一个引用,将之指向堆中的同一对象。
**
原型模式实现步骤
- 定义原型接口,规定克隆接口;
- 定义具体的原型实现对象,实现克隆功能;
- 客户端持有原型接口,并调用其克隆方法进行对象复制;
原型模式最佳实践
使用原型模式创建Person对象。
public class PrototypePattern {private interface IPerson {IPerson clone();}private static class Spouse implements IPerson {private String name;@Overridepublic IPerson clone() {Spouse spouse = new Spouse();spouse.name = this.name;return spouse;}@Overridepublic String toString() {return "Spouse{" +"name='" + name + '\'' +'}';}}private static class Person implements IPerson {private String name;private String nickname;private String identityId;private String gender;private String professional;private Spouse spouse;@Overridepublic IPerson clone() {Person person = new Person();person.name = this.name;person.nickname = this.nickname;person.identityId = this.identityId;person.gender = this.gender;person.professional = this.professional;person.spouse = (Spouse) this.spouse.clone();return person;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", nickname='" + nickname + '\'' +", identityId='" + identityId + '\'' +", gender='" + gender + '\'' +", professional='" + professional + '\'' +", spouse=" + spouse +'}';}}public static void main(String[] args) {Person person = new Person();person.name = "张三";person.nickname = "小三";person.identityId = "123343";person.gender = "male";person.professional = "IT";Spouse spouse = new Spouse();spouse.name = "小七";person.spouse = spouse;System.out.println(person);System.out.println(person.clone());}}
运行结果如下:
:::success Person{name=’张三’, nickname=’小三’, identityId=’123343’, gender=’male’, professional=’IT’, spouse=Spouse{name=’小七’}}
Person{name=’张三’, nickname=’小三’, identityId=’123343’, gender=’male’, professional=’IT’, spouse=Spouse{name=’小七’}} :::
对于Java来说,原型模式的复制功能可以通过实现Cloneable接口并重写clone()方法来实现,重写clone()方法的主要目的是将方法的访问权限设置为public,默认为protected,外界不能方法。
注意:使用clone()方法时,注意深拷贝和浅拷贝的区分,别踩坑了。
