设计模式

设计模式--原型模式

2017-12-04  本文已影响12人  w1992wishes

目录

本文的结构如下:

一、引言

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

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

二、什么是原型模式

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

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

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

三、浅克隆和深克隆

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

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

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

四、clone()

4.1、clone()方法理解

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虚拟机实现下。

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、浅克隆实例

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;
    }

    @Override
    public 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;
    }

    @Override
    public 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仍是指向相同的地址块。

4.4、深克隆实例

实现深克隆,可以在原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;
    }

    @Override
    public 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;
    }

    @Override
    public Object clone(){
        Object o = name;
        try {
            o= super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }

    @Override
    public 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();
    }

    @Override
    public 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;
    }

    @Override
    public 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类图如下:

image

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

六、典型代码

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()); //false
            guyexianzi.setName("姑射仙子");
            guyexianzi.setAge(2985);
            System.out.println(incarnation);
            incarnation.setWife(guyexianzi);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

八、优点和缺点

8.1、优点

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

8.2、缺点

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

九、适用环境

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

上一篇下一篇

猜你喜欢

热点阅读