Amos的随笔

Java/Python/Go,软件测试等等

导航

【Java】从零开始学设计模式:原型模式

定义

原型模式是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的。

适用场景

原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据

优点

  • Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

缺点

  • 需要为每一个类都配置一个 clone 方法
  • clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
  • 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。

实现

Cloneable 接口

浅复制

几点说明

  1. Cloneable 是一个空接口,只是一个标记
  2. 我们重写的clone方法,实际上是重写的 Object 的clone方法 protected native Object clone() throws CloneNotSupportedException;,而这个方法内部实现又是通过本地方法 c/c++来实现的,所以这也是比new一个对象出来要更快的原因
  3. clone方法将创建当前对象类的新实例,并使用此对象对应字段的内容初始化其所有字段,就像赋值一样;字段的内容本身不是克隆的。因此,此方法执行此对象的“浅拷贝”,而不是“深拷贝”操作
@Data
public class Wukong implements Cloneable {

    private Integer age;

    @Override
    public Wukong clone() {
        try {
            return (Wukong) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}
深复制

为什么要做到深复制呢?

如果做不到深复制,那么克隆对象在修改完某个引用属性后,被克隆对象的属性也是会被修改的。如下所示,浅复制对象内部的引用属性(非不可变类:这个后面会说),与被克隆对象内部对应的引用属性,他们指向的同一块堆内存区域。简单一点理解,打个不是特别恰当的比方:

  • 浅复制:孙悟空复制出来的小弟,小弟受伤,孙悟空也会跟着受伤;所以这是危险⚠️的!!!
  • 深复制:孙悟空复制出来的小弟,小弟受伤,跟他没丁点关系
    在这里插入图片描述
public class Immortal implements Cloneable {

    @Setter
    @Getter
    private Wukong wukong;
    @Setter
    @Getter
    private String name;

    @Override
    public Immortal clone() {
        try {
            Immortal i = (Immortal) super.clone();
            if (this.wukong != null) {
                i.setWukong(this.wukong.clone());
            }
            return i;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

由于Wukong属于非不可变类,所以在Immortal复制成功之后,把被克隆对象内部的Wukong的引用也修改为对应的克隆对象。这里需要判空,不然会有空指针异常。

通过序列化复制

如何利用序列化来完成对象的拷贝呢?在内存中通过字节流的拷贝是比较容易实现的。把被克隆对象写入到一个字节流中,再从字节流中将其读出来,这样就可以创建一个新的对象了,并且该新对象与被克隆对象之间并不存在引用共享的问题,真正实现对象的深拷贝。

@Data
public class Person implements Serializable {

    private static final long serialVersionUID = 5947974524451149435L;
    private String name;
    private Integer age;

    public Person copy() {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            oos.flush();
            ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bin);
            bos.close();
            oos.close();
            return (Person) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}

不可变类

参考 Java不可变类

posted on 2019-05-31 08:04  AmosChen  阅读(1)  评论(0编辑  收藏  举报  来源