[关闭]
@w1992wishes 2018-03-13T14:24:11.000000Z 字数 10443 阅读 886

设计模式--原型模式

设计模式 创建型模式


目录

本文的结构如下:

一、引言

我是一个很幼稚的人,所以经常会有很幼稚的想法,比如,有时候上着班我就在想,要是我能够影分身多好,这样,我可以让一号分身陪女朋友和家人,二号分身上班敲代码,三号分身街头卖烤串,四号分身被窝玩游戏......

我的可笑想法在当下是不现实的,但在软件开发中,却是非常务实的。设计模式中有一个模式,可以通过一个原型对象克隆出多个一模一样的对象,该模式称之为原型模式。

二、什么是原型模式

在使用原型模式时,我们需要首先创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。一般这个原型对象的实例化很复杂,需要消耗很多的硬件资源或者数据资源,就像引言中说的“我”,是经过20多年的实例化,消耗了大量的粮食才构造成功的。

原型模式(Prototype Pattern):当创建给定类的实例化过程很复杂或者代价很昂贵时,可以使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。

原型模式的工作原理很简单:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程,拷贝通常是通过克隆方法实现。原型模式是一种“另类”的创建型模式,创建克隆对象的工厂就是原型类自身,工厂方法由克隆方法来实现。

三、浅克隆和深克隆

需要注意的是克隆有深克隆和浅克隆之分。

浅克隆: 在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

深克隆: 在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

四、clone()

4.1、clone()方法理解

clone()方法是Object中的一个方法,其源代码如下:

  1. protected native Object clone() throws CloneNotSupportedException;

可以发现:

JNI是Java Native Interface的 缩写。从Java 1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java虚拟机实现下。

4.2、Java中对象的克隆

一般需要四个步骤:

  1. 在子类中实现Cloneable接口。
  2. 为了获取对象的一份拷贝,我们可以利用Object类的clone方法。
  3. 在子类中覆盖clone方法,声明为public。
  4. 在子类的clone方法中,调用super.clone()。

Cloneable接口仅仅是一个标志接口,而且这个标志也仅仅是针对Object类中 clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出 CloneNotSupportedException 异常。

4.3、浅克隆实例

  1. public class Student implements Cloneable{
  2. private String name;
  3. private int age;
  4. private Professor professor;
  5. public String getName() {
  6. return name;
  7. }
  8. public void setName(String name) {
  9. this.name = name;
  10. }
  11. public int getAge() {
  12. return age;
  13. }
  14. public void setAge(int age) {
  15. this.age = age;
  16. }
  17. public Professor getProfessor() {
  18. return professor;
  19. }
  20. public void setProfessor(Professor professor) {
  21. this.professor = professor;
  22. }
  23. @Override
  24. public String toString() {
  25. return "Student [name=" + name + ", age=" + age + ", professor="
  26. + professor + "]";
  27. }
  28. public Object clone() throws CloneNotSupportedException{
  29. return super.clone();
  30. }
  31. }
  32. public class Professor {
  33. private String name;
  34. private int age;
  35. public String getName() {
  36. return name;
  37. }
  38. public void setName(String name) {
  39. this.name = name;
  40. }
  41. public int getAge() {
  42. return age;
  43. }
  44. public void setAge(int age) {
  45. this.age = age;
  46. }
  47. @Override
  48. public String toString() {
  49. return "Professor [name=" + name + ", age=" + age + "]";
  50. }
  51. }
  52. public class ShadowCopy {
  53. public static void main(String[] args) {
  54. Professor p1 = new Professor();
  55. p1.setName("Professor Zhang");
  56. p1.setAge(30);
  57. Student s1 = new Student();
  58. s1.setName("xiao ming");
  59. s1.setAge(18);
  60. s1.setProfessor(p1);
  61. System.out.println(s1);
  62. try {
  63. Student s2 = (Student) s1.clone();
  64. Professor p2 = s2.getProfessor();
  65. p2.setName("Professor Li");
  66. p2.setAge(45);
  67. s2.setProfessor(p2);
  68. System.out.println("复制后的:s1 = " + s1);
  69. System.out.println("复制后的:s2 = " + s2);
  70. } catch (CloneNotSupportedException e) {
  71. e.printStackTrace();
  72. }
  73. }
  74. }

测试结果会发现复制后打印出来的s1和s2结果是一样的,s1和s2的导师都变成了45岁的Professor Li,显然这并不是期望的结果,产生这个结果的原因lone()方法实现的是浅克隆,对象引用professor只是复制了其引用,s1和s2仍是指向相同的地址块。

4.4、深克隆实例

实现深克隆,可以在原clone()方法基础上改进一下,如下:

  1. public class Student implements Cloneable{
  2. private String name;
  3. private int age;
  4. private Professor professor;
  5. public Student(String name, int age, Professor professor){
  6. this.name = name;
  7. this.age = age;
  8. this.professor = professor;
  9. }
  10. public String getName() {
  11. return name;
  12. }
  13. public void setName(String name) {
  14. this.name = name;
  15. }
  16. public int getAge() {
  17. return age;
  18. }
  19. public void setAge(int age) {
  20. this.age = age;
  21. }
  22. public Professor getProfessor() {
  23. return professor;
  24. }
  25. public void setProfessor(Professor professor) {
  26. this.professor = professor;
  27. }
  28. public Object clone(){
  29. Student o = null;
  30. try {
  31. o = (Student)super.clone();
  32. } catch (CloneNotSupportedException e) {
  33. e.printStackTrace();
  34. }
  35. o.professor = (Professor)professor.clone();
  36. return o;
  37. }
  38. @Override
  39. public String toString() {
  40. return "Student [name=" + name + ", age=" + age + ", professor="
  41. + professor + "]";
  42. }
  43. }
  44. public class Professor implements Cloneable{
  45. private String name;
  46. private int age;
  47. public Professor(String name, int age){
  48. this.name = name;
  49. this.age = age;
  50. }
  51. public String getName() {
  52. return name;
  53. }
  54. public void setName(String name) {
  55. this.name = name;
  56. }
  57. public int getAge() {
  58. return age;
  59. }
  60. public void setAge(int age) {
  61. this.age = age;
  62. }
  63. @Override
  64. public Object clone(){
  65. Object o = name;
  66. try {
  67. o= super.clone();
  68. } catch (CloneNotSupportedException e) {
  69. e.printStackTrace();
  70. }
  71. return o;
  72. }
  73. @Override
  74. public String toString() {
  75. return "Professor [name=" + name + ", age=" + age + "]";
  76. }
  77. }
  78. public class DeepClone {
  79. public static void main(String[] args) {
  80. Professor p=new Professor("wangwu",50);
  81. Student s1=new Student("zhangsan",18, p);
  82. System.out.println(s1);
  83. Student s2=(Student)s1.clone();
  84. s2.getProfessor().setName("maer");
  85. s2.getProfessor().setAge(40);
  86. System.out.println("复制后的:s1 = " + s1);
  87. System.out.println("复制后的:s2 = " + s2);
  88. }
  89. }

也可以利用序列化反序列化来实现深克隆:

  1. public class Student implements Serializable {
  2. private String name;
  3. private int age;
  4. private Professor professor;
  5. public Student(String name, int age, Professor professor){
  6. this.name = name;
  7. this.age = age;
  8. this.professor = professor;
  9. }
  10. public String getName() {
  11. return name;
  12. }
  13. public void setName(String name) {
  14. this.name = name;
  15. }
  16. public int getAge() {
  17. return age;
  18. }
  19. public void setAge(int age) {
  20. this.age = age;
  21. }
  22. public Professor getProfessor() {
  23. return professor;
  24. }
  25. public void setProfessor(Professor professor) {
  26. this.professor = professor;
  27. }
  28. public Object deepClone() throws IOException, ClassNotFoundException {
  29. //将对象写到流中
  30. ByteArrayOutputStream bo=new ByteArrayOutputStream();
  31. ObjectOutputStream oo=new ObjectOutputStream(bo);
  32. oo.writeObject(this);
  33. //从流中读出来
  34. ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
  35. ObjectInputStream oi=new ObjectInputStream(bi);
  36. return oi.readObject();
  37. }
  38. @Override
  39. public String toString() {
  40. return "Student [name=" + name + ", age=" + age + ", professor="
  41. + professor + "]";
  42. }
  43. }
  44. public class Professor implements Serializable {
  45. private String name;
  46. private int age;
  47. public Professor(String name, int age){
  48. this.name = name;
  49. this.age = age;
  50. }
  51. public String getName() {
  52. return name;
  53. }
  54. public void setName(String name) {
  55. this.name = name;
  56. }
  57. public int getAge() {
  58. return age;
  59. }
  60. public void setAge(int age) {
  61. this.age = age;
  62. }
  63. @Override
  64. public String toString() {
  65. return "Professor [name=" + name + ", age=" + age + "]";
  66. }
  67. }
  68. public class DeepClone {
  69. public static void main(String[] args) throws IOException, ClassNotFoundException {
  70. Professor p=new Professor("wangwu",50);
  71. Student s1=new Student("zhangsan",18, p);
  72. System.out.println(s1);
  73. Student s2=(Student)s1.deepClone();
  74. s2.getProfessor().setName("maer");
  75. s2.getProfessor().setAge(40);
  76. System.out.println("复制后的:s1 = " + s1);
  77. System.out.println("复制后的:s2 = " + s2);
  78. }
  79. }

五、模式的结构

介绍完浅克隆和深克隆,再回到原型模式上。

原型模式的UML类图如下:

在原型模式结构图中包含如下几个角色:

六、典型代码

  1. public interface Prototype {
  2. Prototype clone();
  3. void setAttr(String attr);
  4. }
  5. public class ConcretePrototype implements Prototype {
  6. private String attr; //成员属性
  7. public void setAttr(String attr) {
  8. this.attr = attr;
  9. }
  10. public String getAttr() {
  11. return this.attr;
  12. }
  13. public Prototype clone() {
  14. Prototype prototype = new ConcretePrototype(); //创建新对象
  15. prototype.setAttr(this.attr);
  16. return prototype;
  17. /*Object object = null;
  18. try {
  19. object = super.clone();
  20. } catch (CloneNotSupportedException exception) {
  21. System.err.println("Not support cloneable");
  22. }
  23. return (Prototype) object;*/
  24. }
  25. }

clone()方法是不能直接返回this的,相信都明白。

七、代码示例

看过仙侠小说的都知道,修炼到高境界(肯定不是练气,金丹的渣渣了),就可以炼制身外化身,这些化身都有自己的思想,可以修炼自己的功法,但却听从主体的命令,当然有时候化身也会叛变,这里就以仙人化身为例:

  1. public class Immortal implements Serializable {
  2. private String name;
  3. private int age;
  4. private String magicalPower;//神通
  5. private Wife wife;//道侣
  6. public Immortal(String name, int age, String magicalPower) {
  7. try {
  8. Thread.sleep(4000);//模拟仙人修炼
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. this.name = name;
  13. this.age = age;
  14. this.magicalPower = magicalPower;
  15. }
  16. public void setName(String name) {
  17. this.name = name;
  18. }
  19. public void setAge(int age) {
  20. this.age = age;
  21. }
  22. public void setMagicalPower(String magicalPower) {
  23. this.magicalPower = magicalPower;
  24. }
  25. public void setWife(Wife wife) {
  26. this.wife = wife;
  27. }
  28. public String getName() {
  29. return name;
  30. }
  31. public int getAge() {
  32. return age;
  33. }
  34. public String getMagicalPower() {
  35. return magicalPower;
  36. }
  37. public Wife getWife() {
  38. return wife;
  39. }
  40. //使用序列化实现深克隆
  41. public Immortal deepClone() throws IOException, ClassNotFoundException {
  42. //将对象写入流中
  43. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  44. ObjectOutputStream oos = new ObjectOutputStream(bos);
  45. oos.writeObject(this);
  46. //将对象从流中取出
  47. ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
  48. ObjectInputStream ois = new ObjectInputStream(bis);
  49. return (Immortal) ois.readObject();
  50. }
  51. public String toString() {
  52. return "仙人 [姓名=" + name + ", 年龄=" + age + ", 神通="
  53. + magicalPower + ",道侣=" + wife.getName() + "]";
  54. }
  55. }
  56. public class Wife implements Serializable{
  57. private String name;
  58. private int age;
  59. public Wife(String name, int age){
  60. this.name = name;
  61. this.age = age;
  62. }
  63. public void setName(String name) {
  64. this.name = name;
  65. }
  66. public void setAge(int age) {
  67. this.age = age;
  68. }
  69. public String getName() {
  70. return name;
  71. }
  72. public int getAge() {
  73. return age;
  74. }
  75. public String toString(){
  76. return "道侣 [姓名=" + name + ", 年龄=" + age + "]";
  77. }
  78. }
  79. public class Client {
  80. public static void main(String[] args) {
  81. Wife yushiqie = new Wife("雨师妾", 3000);
  82. Immortal immortal = new Immortal("拓拔野", 2985, "天元诀,刹那芳华");
  83. immortal.setWife(yushiqie);
  84. System.out.println(immortal);
  85. try {
  86. //故事最后拓跋陪我最爱的雨师妾归隐,但姑射仙子却没了归宿,这里假设拓跋分出一个分身
  87. Immortal incarnation = immortal.deepClone();
  88. Wife guyexianzi = incarnation.getWife();
  89. System.out.println(immortal.getWife() == incarnation.getWife()); //false
  90. guyexianzi.setName("姑射仙子");
  91. guyexianzi.setAge(2985);
  92. System.out.println(incarnation);
  93. incarnation.setWife(guyexianzi);
  94. } catch (IOException e) {
  95. e.printStackTrace();
  96. } catch (ClassNotFoundException e) {
  97. e.printStackTrace();
  98. }
  99. }
  100. }

八、优点和缺点

8.1、优点

原型模式的主要优点如下:

8.2、缺点

原型模式的主要缺点如下:

九、适用环境

在以下情况下可以考虑使用原型模式:

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注