原型设计模式

👉 请投票支持这款 全新设计的脚手架 ,让 Java 再次伟大!
在这里插入图片描述

什么是原型设计模式

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

原型设计模式怎么用

原型设计模式没有复杂的概念,它就是用来拷贝对象的。
在 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 架构与 fina l 设计理念冲突的原因,你又无法将你的类设计为不可变类!

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

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

总结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值