@w1992wishes
        
        2018-03-13T06:24:11.000000Z
        字数 10443
        阅读 1036
    设计模式 创建型模式
本文的结构如下:
我是一个很幼稚的人,所以经常会有很幼稚的想法,比如,有时候上着班我就在想,要是我能够影分身多好,这样,我可以让一号分身陪女朋友和家人,二号分身上班敲代码,三号分身街头卖烤串,四号分身被窝玩游戏......
我的可笑想法在当下是不现实的,但在软件开发中,却是非常务实的。设计模式中有一个模式,可以通过一个原型对象克隆出多个一模一样的对象,该模式称之为原型模式。
在使用原型模式时,我们需要首先创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。一般这个原型对象的实例化很复杂,需要消耗很多的硬件资源或者数据资源,就像引言中说的“我”,是经过20多年的实例化,消耗了大量的粮食才构造成功的。
原型模式(Prototype Pattern):当创建给定类的实例化过程很复杂或者代价很昂贵时,可以使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。
原型模式的工作原理很简单:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程,拷贝通常是通过克隆方法实现。原型模式是一种“另类”的创建型模式,创建克隆对象的工厂就是原型类自身,工厂方法由克隆方法来实现。
需要注意的是克隆有深克隆和浅克隆之分。
浅克隆: 在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
深克隆: 在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
clone()方法是Object中的一个方法,其源代码如下:
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虚拟机实现下。
一般需要四个步骤:
Cloneable接口仅仅是一个标志接口,而且这个标志也仅仅是针对Object类中 clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出 CloneNotSupportedException 异常。
public class Student implements Cloneable{private String name;private int age;private Professor professor;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Professor getProfessor() {return professor;}public void setProfessor(Professor professor) {this.professor = professor;}@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + ", professor="+ professor + "]";}public Object clone() throws CloneNotSupportedException{return super.clone();}}public class Professor {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Professor [name=" + name + ", age=" + age + "]";}}public class ShadowCopy {public static void main(String[] args) {Professor p1 = new Professor();p1.setName("Professor Zhang");p1.setAge(30);Student s1 = new Student();s1.setName("xiao ming");s1.setAge(18);s1.setProfessor(p1);System.out.println(s1);try {Student s2 = (Student) s1.clone();Professor p2 = s2.getProfessor();p2.setName("Professor Li");p2.setAge(45);s2.setProfessor(p2);System.out.println("复制后的:s1 = " + s1);System.out.println("复制后的:s2 = " + s2);} catch (CloneNotSupportedException e) {e.printStackTrace();}}}
测试结果会发现复制后打印出来的s1和s2结果是一样的,s1和s2的导师都变成了45岁的Professor Li,显然这并不是期望的结果,产生这个结果的原因lone()方法实现的是浅克隆,对象引用professor只是复制了其引用,s1和s2仍是指向相同的地址块。
实现深克隆,可以在原clone()方法基础上改进一下,如下:
public class Student implements Cloneable{private String name;private int age;private Professor professor;public Student(String name, int age, Professor professor){this.name = name;this.age = age;this.professor = professor;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Professor getProfessor() {return professor;}public void setProfessor(Professor professor) {this.professor = professor;}public Object clone(){Student o = null;try {o = (Student)super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}o.professor = (Professor)professor.clone();return o;}@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + ", professor="+ professor + "]";}}public class Professor implements Cloneable{private String name;private int age;public Professor(String name, int age){this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic Object clone(){Object o = name;try {o= super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return o;}@Overridepublic String toString() {return "Professor [name=" + name + ", age=" + age + "]";}}public class DeepClone {public static void main(String[] args) {Professor p=new Professor("wangwu",50);Student s1=new Student("zhangsan",18, p);System.out.println(s1);Student s2=(Student)s1.clone();s2.getProfessor().setName("maer");s2.getProfessor().setAge(40);System.out.println("复制后的:s1 = " + s1);System.out.println("复制后的:s2 = " + s2);}}
也可以利用序列化反序列化来实现深克隆:
public class Student implements Serializable {private String name;private int age;private Professor professor;public Student(String name, int age, Professor professor){this.name = name;this.age = age;this.professor = professor;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Professor getProfessor() {return professor;}public void setProfessor(Professor professor) {this.professor = professor;}public Object deepClone() throws IOException, ClassNotFoundException {//将对象写到流中ByteArrayOutputStream bo=new ByteArrayOutputStream();ObjectOutputStream oo=new ObjectOutputStream(bo);oo.writeObject(this);//从流中读出来ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());ObjectInputStream oi=new ObjectInputStream(bi);return oi.readObject();}@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + ", professor="+ professor + "]";}}public class Professor implements Serializable {private String name;private int age;public Professor(String name, int age){this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Professor [name=" + name + ", age=" + age + "]";}}public class DeepClone {public static void main(String[] args) throws IOException, ClassNotFoundException {Professor p=new Professor("wangwu",50);Student s1=new Student("zhangsan",18, p);System.out.println(s1);Student s2=(Student)s1.deepClone();s2.getProfessor().setName("maer");s2.getProfessor().setAge(40);System.out.println("复制后的:s1 = " + s1);System.out.println("复制后的:s2 = " + s2);}}
介绍完浅克隆和深克隆,再回到原型模式上。
原型模式的UML类图如下:

在原型模式结构图中包含如下几个角色:
public interface Prototype {Prototype clone();void setAttr(String attr);}public class ConcretePrototype implements Prototype {private String attr; //成员属性public void setAttr(String attr) {this.attr = attr;}public String getAttr() {return this.attr;}public Prototype clone() {Prototype prototype = new ConcretePrototype(); //创建新对象prototype.setAttr(this.attr);return prototype;/*Object object = null;try {object = super.clone();} catch (CloneNotSupportedException exception) {System.err.println("Not support cloneable");}return (Prototype) object;*/}}
clone()方法是不能直接返回this的,相信都明白。
看过仙侠小说的都知道,修炼到高境界(肯定不是练气,金丹的渣渣了),就可以炼制身外化身,这些化身都有自己的思想,可以修炼自己的功法,但却听从主体的命令,当然有时候化身也会叛变,这里就以仙人化身为例:
public class Immortal implements Serializable {private String name;private int age;private String magicalPower;//神通private Wife wife;//道侣public Immortal(String name, int age, String magicalPower) {try {Thread.sleep(4000);//模拟仙人修炼} catch (InterruptedException e) {e.printStackTrace();}this.name = name;this.age = age;this.magicalPower = magicalPower;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public void setMagicalPower(String magicalPower) {this.magicalPower = magicalPower;}public void setWife(Wife wife) {this.wife = wife;}public String getName() {return name;}public int getAge() {return age;}public String getMagicalPower() {return magicalPower;}public Wife getWife() {return wife;}//使用序列化实现深克隆public Immortal deepClone() throws IOException, ClassNotFoundException {//将对象写入流中ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);//将对象从流中取出ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return (Immortal) ois.readObject();}public String toString() {return "仙人 [姓名=" + name + ", 年龄=" + age + ", 神通="+ magicalPower + ",道侣=" + wife.getName() + "]";}}public class Wife implements Serializable{private String name;private int age;public Wife(String name, int age){this.name = name;this.age = age;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public int getAge() {return age;}public String toString(){return "道侣 [姓名=" + name + ", 年龄=" + age + "]";}}public class Client {public static void main(String[] args) {Wife yushiqie = new Wife("雨师妾", 3000);Immortal immortal = new Immortal("拓拔野", 2985, "天元诀,刹那芳华");immortal.setWife(yushiqie);System.out.println(immortal);try {//故事最后拓跋陪我最爱的雨师妾归隐,但姑射仙子却没了归宿,这里假设拓跋分出一个分身Immortal incarnation = immortal.deepClone();Wife guyexianzi = incarnation.getWife();System.out.println(immortal.getWife() == incarnation.getWife()); //falseguyexianzi.setName("姑射仙子");guyexianzi.setAge(2985);System.out.println(incarnation);incarnation.setWife(guyexianzi);} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}}
原型模式的主要优点如下:
原型模式的主要缺点如下:
在以下情况下可以考虑使用原型模式: