原型模式(Prototype Pattern),使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。原型模式会要求对象实现一个可以“克隆”自身的接口,这样就可以通过拷贝或者是克隆一个实例对象本身,来创建一个新的实例。如果把这个方法定义在接口上,看起来就像是通过接口来创建了新的接口对象。

在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,因为原型模式是基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。

原型模式的组成为:

组成 作用
Prototype 声明一个克隆自身的接口,用来约束想要克隆自己的类,要求它们都要实现这里定义的克隆方法。
ConcretePrototype) 实现Prototype接口的类,这些类真正实现了克隆自身的功能。

原型模式的优点

  1. Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良
  2. 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作

原型模式的缺点

  1. 需要为每一个类都配置一个 clone 方法
  2. clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则
  3. 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当

深克隆和浅克隆

  • 浅克隆

创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址

  • 深克隆

创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址

  1. public class CloneTest {
  2. private static class RefClass implements Cloneable {
  3. private String filed;
  4. private RefClass(String filed) {
  5. this.filed = filed;
  6. }
  7. @Override
  8. protected RefClass clone() throws CloneNotSupportedException {
  9. return ((RefClass) super.clone());
  10. }
  11. }
  12. private static class TestClass implements Cloneable {
  13. private RefClass filed;
  14. private TestClass(RefClass filed) {
  15. this.filed = filed;
  16. }
  17. @Override
  18. protected TestClass clone() throws CloneNotSupportedException {
  19. return ((TestClass) super.clone());
  20. }
  21. private TestClass deepClone() throws CloneNotSupportedException {
  22. TestClass clone = (TestClass) super.clone();
  23. // 引用类型也要克隆
  24. clone.filed = filed.clone();
  25. return clone;
  26. }
  27. }
  28. public static void main(String[] args) throws CloneNotSupportedException {
  29. RefClass refClass = new RefClass("引用类型");
  30. TestClass testClass = new TestClass(refClass);
  31. TestClass clone = testClass.clone();
  32. System.out.println("浅克隆的结果:\n");
  33. System.out.println("testClass == clone: " + (testClass == clone));
  34. System.out.println("\ntestClass.filed == clone.filed: " + (testClass.filed == clone.filed));
  35. clone = testClass.deepClone();
  36. System.out.println("\n深克隆的结果:\n");
  37. System.out.println("testClass == clone: " + (testClass == clone));
  38. System.out.println("\ntestClass.filed == clone.filed: " + (testClass.filed == clone.filed));
  39. }
  40. }

运行结果如下:

:::success 浅克隆的结果:
testClass == clone: false testClass.filed == clone.filed: true
深克隆的结果:
testClass == clone: false
testClass.filed == clone.filed: false :::

由此可见,浅克隆得到的新对象,两者使用的是同一个对象,当某一个对象发生改变时,另一个也会发生改变。因为对于成员变量为引用类型的数据来说,clone只是创建出一个引用,将之指向堆中的同一对象。
**

原型模式实现步骤

  1. 定义原型接口,规定克隆接口;
  2. 定义具体的原型实现对象,实现克隆功能;
  3. 客户端持有原型接口,并调用其克隆方法进行对象复制;

原型模式最佳实践

使用原型模式创建Person对象。

  1. public class PrototypePattern {
  2. private interface IPerson {
  3. IPerson clone();
  4. }
  5. private static class Spouse implements IPerson {
  6. private String name;
  7. @Override
  8. public IPerson clone() {
  9. Spouse spouse = new Spouse();
  10. spouse.name = this.name;
  11. return spouse;
  12. }
  13. @Override
  14. public String toString() {
  15. return "Spouse{" +
  16. "name='" + name + '\'' +
  17. '}';
  18. }
  19. }
  20. private static class Person implements IPerson {
  21. private String name;
  22. private String nickname;
  23. private String identityId;
  24. private String gender;
  25. private String professional;
  26. private Spouse spouse;
  27. @Override
  28. public IPerson clone() {
  29. Person person = new Person();
  30. person.name = this.name;
  31. person.nickname = this.nickname;
  32. person.identityId = this.identityId;
  33. person.gender = this.gender;
  34. person.professional = this.professional;
  35. person.spouse = (Spouse) this.spouse.clone();
  36. return person;
  37. }
  38. @Override
  39. public String toString() {
  40. return "Person{" +
  41. "name='" + name + '\'' +
  42. ", nickname='" + nickname + '\'' +
  43. ", identityId='" + identityId + '\'' +
  44. ", gender='" + gender + '\'' +
  45. ", professional='" + professional + '\'' +
  46. ", spouse=" + spouse +
  47. '}';
  48. }
  49. }
  50. public static void main(String[] args) {
  51. Person person = new Person();
  52. person.name = "张三";
  53. person.nickname = "小三";
  54. person.identityId = "123343";
  55. person.gender = "male";
  56. person.professional = "IT";
  57. Spouse spouse = new Spouse();
  58. spouse.name = "小七";
  59. person.spouse = spouse;
  60. System.out.println(person);
  61. System.out.println(person.clone());
  62. }
  63. }

运行结果如下:

:::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()方法时,注意深拷贝和浅拷贝的区分,别踩坑了。