跳至主要內容

原型模式

AruNi_Lu设计模式设计模式与范式约 1112 字大约 4 分钟

本文内容

1. 什么是原型模式

原型模式也属于创建型模式,原型模式按照着字面意思来理解就好了,即 基于原型来创建对象的方式 就叫做 原型设计模式

原型模式创建的新对象基于原型,那说明 新对象与原对象的差别不是很大,所以可以 直接采用复制(或叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。但如果新对象本身的创建就十分简单,只消耗一点儿时间,那就没必要采用复制的方式创建了。

所以可以得出原型模式的 应用场景对象的创建成本比较大,需要花费太多的时间,并且 新对象与原对象之间的差别不大时,便可以考虑原型模式。

何为 “对象的创建成本比较大”?

如何定义这个创建成本呢?什么样对象的创建过程才算成本大呢?可以从下面的几种情况来判断:

  • 创建的对象中的数据需要经过 复杂的计算 才能得到(如排序、计算哈希值);
  • 创建的对象中的数据需要从网络、数据库、文件系统等 非常慢速的 IO 操作 中读取。

如果需要创建的对象满足上述情况中的一种,创建对象消耗的时间就比较长了,自然创建的成本就比较大了。

2. 原型模式的实现方式

上面讲到,通过复制的方式创建对象,就是原型模式,那么实现方式肯定就是复制嘛。

在 Java 中,使用 Object#clone() 方法可以很方便的复制一个对象,不过需要注意 浅拷贝和深拷贝 的问题。

深拷贝和浅拷贝的区别在于:

  • 浅拷贝只会拷贝对基本数据类型的数据和 引用对象类型的内存地址,而 不会拷贝引用对象本身
  • 而深拷贝除了会拷贝上面浅拷贝的数据外,对于引用类型的变量也会重新复制一份出来,而不是简单的引用地址值

举个例子,Person 类中有一个 Address 类:

  • 如果只是使用浅拷贝简单的克隆一个 person 对象,那么这个拷贝对象和原对象是共用一个 Address 的,因为只是拷贝了 Address 的引用地址;
  • 但如果是使用深拷贝,那么这两个 Person 类对象都会有各自的 Address。

对比图如下:

image-20230404215620959

想使用深拷贝也很简单,在重写 clone() 方法时,直接将所有引用类型对象重新 new 一个,再重新设置即可;或者 将引用类型的对象也调用一下 clone() 方法即可。就像下面这样:

@Override
public Person clone() {
    try {
        Person person = (Person) super.clone();
        // 把 Address 也克隆一下
        person.setAddress(person.getAddress().clone());
        return person;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}

除此之外,还有另一种方式也能 实现深拷贝,那就是使用 序列化:先将对象序列化,然后再反序列化成新的对象。具体实现如下:

public Object deepCopy(Object object) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bo);
        oo.writeObject(object);

        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi = new ObjectInputStream(bi);

        return oi.readObject();
    }

所以,对于原型模式,可以通过 浅拷贝、深拷贝(有需要)和序列化的方式实现,如果新对象与原型对象有一点点差异,那么在拷贝完后重新设置一下变量值即可。

3. 总结

再来回顾一下原型模式的应用场景:对象的创建成本比较大,需要花费太多的时间,并且 新对象与原对象之间的差别不大时,便可以考虑原型模式。

实现原型模式也很简单,分别可以通过 浅拷贝、深拷贝(有需要)和序列化 的方式实现。

上次编辑于: