原型模式

61 阅读4分钟

模式介绍

用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。

模式图解

原理类图

image.png

类图说明

原型模式包含如下角色:

  • 抽象原型类:规定了具体原型对象必须实现的clone()方法。对应类图中的Prototype接口。
  • 具体原型类:实现抽象原型类的clone()方法,它是可被复制的对象。对应类图中的Realizetype类。
  • 访问类:使用具体原型类中的clone()方法来复制新的对象。对应类中的PrototypeTest类。

两种实现方式

浅拷贝

1、对于数据类型是基本类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
2、对于数据类型是引用数据类型的成员变量,比如数组、对象等,那么浅拷贝会进行引用传递,也就只是将该成员变量的引用值(内存地址)复制一份给新的对象,因为实际上两个对象的该成员变量都指向同一个实例,在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

浅拷贝可以使用默认的clone()方法来是实现:

XXX xxx = (XXX) super.clone();

深拷贝

复制对象的所有基本数据类型的成员变量值;
为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝。

深拷贝实现方式:

  • 重写clone方法来实现深拷贝
  • 通过对象序列化实现深拷贝【推荐】

深拷贝之重写 clone

package model;

import java.io.*;
import java.util.Objects;

/**
 * 原型对象
 * 
 * @author asyyr
 */
public class Sheep implements Cloneable, Serializable {

    private static final long serialVersionUID = 460754667710L;

    private String name;
    private int age;
    private String color;
    private Grass grass;

    public Sheep(String name, int age, String color, Grass grass) {
        this.name = name;
        this.age = age;
        this.color = color;
        this.grass = grass;
    }

    @Override
    protected Sheep clone() {
        Sheep sheep = null;
        try {
            sheep = (Sheep) super.clone();
            sheep.grass = grass.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.getMessage());
        }
        return sheep;
    }
... get/set/toString
}
package model;

import java.io.Serializable;

/**
 * 原型对象
 * 
 * @author asyyr
 */
public class Grass implements Cloneable, Serializable {

    private static final long serialVersionUID = 470754667710L;

    private String name;
    private String color;

    public Grass(String name, String color) {
        this.name = name;
        this.color = color;
    }

    @Override
    protected Grass clone() {
        Grass grass = null;
        try {
            grass = (Grass) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.getMessage());
        }
        return grass;
    }
    ... get/set/toString
}

深拷贝之对象序列化

package model;

import java.io.*;
import java.util.Objects;

/**
 * 原型对象
 * 
 * @author asyyr
 */
public class Sheep implements Serializable {

    private static final long serialVersionUID = 460754667710L;

    private String name;
    private int age;
    private String color;
    private Grass grass;

    public Sheep(String name, int age, String color, Grass grass) {
        this.name = name;
        this.age = age;
        this.color = color;
        this.grass = grass;
    }

    protected Sheep deepClone() throws IOException {
        Sheep sheep = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(this);
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            sheep = (Sheep) ois.readObject();
        } catch (ClassNotFoundException e) {
            System.out.println("deepClone exception:" + e.getMessage());
            return sheep;
        } finally {
            if (Objects.nonNull(ois)) {
                ois.close();
            }
            if (Objects.nonNull(bis)) {
                bis.close();
            }
        }
        return sheep;
    }
    ... get/set/toString
}

实现原理

通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone()

优点

  • 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率;
  • 不用重新初始化对象,而是动态地获得对象运行时的状态;
  • 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码;

缺点

  • 在实现深克隆的时候可能需要比较复杂的代码;
  • 需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了OCP原则,特别是当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

不可串行化的类

一般而言,满足下面的四个条件之一的类就不应当串行化:

  1. 一个类与本地代码(native code)有紧密的关系。比如 java.util.zip.Deflater。
  2. 对象的内部状态依赖于 Java 虚拟机或者运行环境,从而在每一次运行时这个状态都有可能不同。比如 java.lang.Thread,java.io.InputStream,java.io.FileDescriptor,java.awt.PrintJob 等。
  3. 串行化可能带来潜在的安全隐患。比如,java.lang.SecurityManager 以及 java.security.MessgeDigest 等。
  4. 一个类仅仅是一些静态方法的存放地,并没有任何的内部状态。比如 java.beans.Beans 和 java.lang.Math 等。

注意事项

  1. 有些对象,比如 Thread 对象或者 Socket 对象,是不能简单复制或者共享的,不管是使用深复制还是浅复制,只要涉及这样的间接对象,就必须把间接对象设置为 transient 而不予复制;