原型设计模式


什么是原型设计模式?

用原型实例指定创建对象的种类,并通过拷贝这些原型创建包含了原对象中所有信息的新的对象。 原型设计模式在某种条件下,是一种非常危险的,难以驾驭的设计模式。使用应该慎之又慎。

原型设计模式怎么用?

原型设计模式没有复杂的概念,它就是用来拷贝对象的。 在java中若想使一个对象能够被拷贝,基本上有下面这几种方法:

  • 实现Cloneable接口。
  • 重写clone()方法,并调用super.clone()方法获取到拷贝对象
  • 若目标对象包含复杂对象引用,则继续调用引用对象的clone()方法,再将结果set到目标对象上去。
  • 针对复杂的,引用较深的对象,使用内存流+序列化的方式,直接获取到完全拷贝对象。
/**
 * 原型设计模式
 */
public class Prototype implements Cloneable, Serializable {

    private static final long serialVersionUID = 8196154781151609930L;

    private ArrayList<String> arrayList = new ArrayList<>();

    private Object[] models = {new Object(), new Object()};

    // final与clone价架构不兼容。因为你无法在初始化以外的地方改变final的引用
    private final Prototype prototype = new Prototype();



    /**
     * 重写Object的clone方法
     *
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        Prototype prototype = (Prototype) super.clone();
        prototype.arrayList = (ArrayList<String>) this.arrayList.clone();
        prototype.models = this.models.clone();
        //this.prototype = (Prototype) prototype.clone();
        return prototype;
    }

    /**
     * 通过对象序列化进行深拷贝
     *
     * @return
     */
    public Prototype deepClone() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream outputStream = new ObjectOutputStream(bos);

        outputStream.writeObject(this);

        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);

        return (Prototype) ois.readObject();
    }

}

原型设计模式的设计缺陷和安全隐患

为什么文章开头说,原型设计模式在某种条件下,是一种非常危险的,难以驾驭的设计模式?这需要从Cloneable接口糟糕的设计说起。

首先,Cloneable接口没有包含任何方法,实现它的作用是改变了他的父类中受保护的clone方法的实现行为,使得Object的clone方法能够返回一个拷贝对象。这样的接口设计颠覆了java的接口设计理念。 往往实现一个接口,是为了告诉客户这个类能够为他做些什么。而Cloneable却改变了父类的clone方法的行为。这样极端的做法,基本上可以用匪夷所思来形容。

其次,调用了java语言以外的方法创建了对象,同时深拷贝浅拷贝的问题带来了巨大的安全隐患。比如下面这一段代码:

 /**
     * 测试
     *
     * @param args
     */
    public static void main(String[] args) {
        Prototype prototype = new Prototype();
        System.out.println(prototype.models[0]);
        Object object1 = prototype.models[0];

        try {
            Prototype clonePrototype = (Prototype) prototype.clone();
            System.out.println(clonePrototype.models[0]);
            Object object2 = prototype.models[0];
            System.out.println(object1 == object2);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

输出结果

java.lang.Object@63947c6b java.lang.Object@63947c6b true

这太可怕了。你不得不仔细检查你的每一个属性,直到你确保他们的clone都得到调用为止。

接着,如果你的类设计为了一个不可变类,或者类中有final字段的话,那么恭喜,clone架构与引用可变对象的final是不兼容的,clone方法无法为final字段赋值。因为你无法在初始化以外的地方重新改变引用。

然后,clone方法“意料之中”的不是同步的。这意味着如果想让你的对象在并发中安全的话,则得花心思在clone方法的同步处理上。而由于clone架构与final设计理念冲突的原因,你又无法将你的类设计为不可变类!

最后,如果你的对象是出于被继承的目的而被设计出来的话,那么你的clone方法总是会被所有子类重写。所以建议被继承的类保持与Object的设计一致,不主动实现Cloneable方法。但是你需要覆盖clone方法,重写clone逻辑,方法保持protected的访问权限。这样做得以让子类拥有是否实现接口的自由,并且还能保证你的类能够拥有正确的“深拷贝”行为。

所以,如果你想要写出健壮,安全,维护性强的代码,你就应该考虑告别Cloneable接口。这个世界上,有些经验丰富的程序员永远不会实现Cloneable接口。

总结

  1. 原型模式常常和其他设计模式搭配使用。
  2. 注意深浅拷贝的区别。
  3. 鉴于Cloneable糟糕的设计,对于任何自定义类,如果你没有充足的把握,请不要实现Cloneable接口。凡是实现了Cloneable接口的自定义类,一定要给用户提供安全的clone实现。
  4. 如果你的自定义类需要并发安全,请花心思对clone方法进行同步处理。
  5. 复杂对象建议使用序列化+流的方式重写。这相当安全,并且不容易出错。

Copyright 2017/08/15 by Chuck Lin

若文章有幸帮到了您,您可以捐助我,以鼓励我写出更棒的作品!

alipay.jpg-17.7kBwechat.jpg-16.7kB

Copyright © chuck Lin 2017 all right reserved,powered by Gitbook该文件修订时间: 2018-07-15 10:39:59

results matching ""

    No results matching ""