[设计模式] 创建型:原型模式(Prototype Pattern)

什么是原型模式

听说过克隆技术吧?再通俗点,就是复制。

原型模式就是代码里面的克隆技术,把一个已经实例化的对象当作原型,克隆出一个跟它相同的对象。

可以采用new方式构造一个新对象,再把原型对象的内部状态拷贝过来,达到复制原型对象的目的,但是这就涉及关心到了原型内部,没有克隆模式来的简单直接。

原型模式的实现思路

从是否具备克隆能力的角度看,有两种实现思路:

  1. 原型自身具备克隆能力,就像孙悟空,拔根猴毛就能变出一个分身
  2. 原型自身不具备克隆能力,就像克隆羊多利,是生物学家掌握克隆技术,从母体身上克隆出来的

Java代码实现角度看,有三种实现方案:

  1. 利用Object类提供的clone方法实现,有浅克隆、深克隆两种模式
  2. 利用对象序列化机制实现,只有深克隆模式
  3. 还有一种方法就当发散思维了,那就是直接new新对象,再将原型模式属性都复制过来(实际不这么干,也不推荐)

clone()

因为Object类里面提供有clone方法,而Object类又是所有类的隐形基类,所以,通过继承的途径,每个对象就天然拥有自己克隆自己的潜力。

但是这个clone()只能做到浅克隆模式,要做到深克隆还需要自己动手多做点事情。

序列化

序列化就是把内存中的对象信息存储起来,需要的时候,再通过反序列化将储存信息转化成内存中的对象信息。

这句话有两个要点:

  1. 序列化是转换内存对象信息的过程,不包括对象的内存地址信息
  2. 反序列化是根据对象信息重建内存对象的过程

因此,这种方法只能实现深克隆模式,不能实现浅克隆模式。

原型模式的实现代码

浅克隆

浅克隆的意思是:克隆出来的对象,所有“基本类型变量值”和“引用类型变量值”都和原型对象一样。

也就是说,如果原型对象中有一个属性指向对象A,那么克隆对象相同的属性也指向对象A

浅克隆只复制原型对象、以及原型对象属性的基本类型值和引用值,不会复制内部属性引用的其它对象。

代码实现分两步走:

  1. 原型类实现java.lang.Cloneable接口,这是一个空的标记接口,并无实际作用,只是遵循Java的编程规范,表明这类对象可以克隆。
  2. 原型类重写Object类中的clone方法,并将其访问修饰符变更为public,重写逻辑里面需要调用Objectclone方法,也就是super.clone()

代码实现:

public class Teacher {
}

// 原型
public class Student implements Cloneable {
    private Teacher teacher;

    public Student(Teacher teacher) {
        this.teacher = teacher;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    @Override
    public Student clone() throws CloneNotSupportedException {
        return (Student) super.clone();
    } 
}

// 测试
public static void main(String[] args) throws CloneNotSupportedException {
    Student student = new Student(new Teacher());// 原型对象
    Student clone = student.clone();// 克隆对象
    System.out.println(student.getTeacher() == clone.getTeacher());// true
}

深克隆

深克隆的意思是:克隆出来的对象,所有“基本类型变量值”和原型对象相同,“引用类型变量值”和原型对象不同。

也就是说,如果原型对象中有一个属性指向对象A,那么,对象A也要被克隆,最后,克隆对象相同的属性不是指向A对象,而是指向新对象A'

深克隆不仅复制原型对象,还会复制原型对象内部属性的引用对象,克隆的特别透彻。

实现深克隆,就在浅克隆基础上多做一点事情:手动把原型所有的引用属性再克隆一次。

前面说了,深克隆有两种方法:clone ()、序列化。

第一种clone(),代码实现:

public class Teacher implements Cloneable {
    @Override
    protected Teacher clone() throws CloneNotSupportedException {
        return (Teacher) super.clone();
    }
}

// 原型
public class Student implements Cloneable {
    private Teacher teacher;

    public Student(Teacher teacher) {
        this.teacher = teacher;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    @Override
    public Student clone() throws CloneNotSupportedException {
        Student clone = (Student) super.clone();
        clone.teacher = teacher.clone(); // 手动赋值,深克隆需要自己编码多做事情
        return clone;
    }
}

// 测试
public static void main(String[] args) throws CloneNotSupportedException {
    Student student = new Student(new Teacher());// 原型对象
    Student clone = student.clone();// 克隆对象
    System.out.println(student.getTeacher() == clone.getTeacher());// false
}

第二种序列化,需要注意一点,通过这种方式进行的深克隆,相关类都需要实现java.io.Serializable接口,这是Java序列化机制要求的。代码实现:

public class Teacher implements Serializable {
}

// 原型
public class Student implements Serializable {
    private Teacher teacher;

    public Student(Teacher teacher) {
        this.teacher = teacher;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    // 因为没有利用Object的clone方法,所以,最好也别重写clone方法,新定义一个方法比较好
    public Student 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 (Student) ois.readObject();
    }
}

// 测试
public static void main(String[] args) throws IOException, ClassNotFoundException {
    Student student = new Student(new Teacher());
    Student clone = student.deepClone();
    System.out.println(student.getTeacher() == clone.getTeacher());
}

原型模式的优缺点

优点

一旦构建好原型模式的克隆逻辑,就可以很方便的得到一个和原型一摸一样的对象,不需要关心原型的内部信息。

缺点

需要理解浅克隆和深克隆的含义,不能在这上面犯错。

克隆逻辑往往具有全局影响力,设计的时候需要全盘考量,对原来的克隆逻辑进行改动更是要慎之又慎!

当对象引用属性包含着循环结构的时候,特别容易出问题!

采用序列化机制的时候,还要考虑对象引用属性是否都能继承Serializable接口,如果属性对象是第三方jar包里的,很可能就会出现不支持序列化的问题,要慎重!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章