Skip to content

原型设计模式是一种对象创建型模式,使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,主要用于创建重复的对象,同时又能保证性能

应用场景刨析

如果你有一个对象并希望生成与其完全相同的一个复制品,你该如何实现呢?首先你必须新建一个属于相同类的对象。然后你必须遍历原始对象的所有成员变量, 并将成员变量值复制到新对象中。

不错! 但有个小问题。 并非所有对象都能通过这种方式进行复制, 因为有些对象可能拥有私有成员变量, 它们在对象本身以外是不可见的。“从外部” 复制对象并非总是可行。

从外部复制对象会遇到什么问题?

直接复制还有另外一个问题。 因为你必须知道对象所属的类才能创建复制品, 所以代码必须依赖该类。 即使你可以接受额外的依赖性, 那还有另外一个问题: 有时你只知道对象所实现的接口, 而不知道其所属的具体类, 比如可向方法的某个参数传入实现了某个接口的任何对象。

核心组成

工作原理是将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。应该是最简单的设计模式了,实现一个接口,重写一个方法即完成了原型模式。

  • Prototype: 声明克隆方法的接口,是所有具体原型类的公共父类,Cloneable接口
  • ConcretePrototype : 具体原型类
  • Client: 让一个原型对象克隆自身从而创建一个新的对象

应用场景

  • 创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得
  • 如果系统要保存对象的状态,做备份使用

编码实现

定义一个对象,实现Cloneable接口,重写clone方法

java
public class Person implements Cloneable {

    private String name;

    public Person() {
        System.out.println("构造方法被调用了");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person)super.clone();
    }
}

使用:

java
public static void main(String[] args) throws CloneNotSupportedException {
    Person person = new Person();
    person.setName("张三");

    Person person1 = person.clone();
    person.setName("李四");
    
     Person person1 = person.clone();
    person.setName("王五");
}

观察控制台,只有一句话:构造方法被调用了,证明构造方法只被调用了一次。

场景说明:如果Person对象里面有几十种属性,但只需要就该其中的几个,使用原型设计模式会比较方便。

看起来是不是很简单?别着急还没完呢,如果里面是一个复杂的数据类型呢?例如对象为List,通过DEBUG模式发现,拷贝后的内存地址是一样的,因此当一个对象修改了List内容,其他对象都会同步修改,如果我们不想同步修改该怎么办呢?

浅拷贝与深拷贝

  • 浅拷贝:如果原型对象的成员变量是基本数据类型(int、double、byte、boolean、char等),将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址通过覆盖Object类的clone()方法可以实现浅克隆
  • 深拷贝:无论原型对象的成员变量是基本数据类型还是引用类型,都将复制一份给克隆对象,如果需要实现深克隆,可以通过序列化(Serializable)等方式来实现

TIP

原型模式是内存二进制流的拷贝,比new对象性能高很多,使用的时候记得注意是选择浅拷贝还是深拷贝

在这里插入图片描述

原型设计模式之深拷贝实现

我们让拷贝的对象进行一次序列化与反序列化,这样内存地址就改变了。既然涉及到了序列化就需要实现Serializable接口

java
public class Person implements Cloneable, Serializable {

    private String name;
    private List<String> myList;

    public Person() {
        System.out.println("构造方法被调用了");
    }

   // ------------get&&set------------

    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person)super.clone();
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", myList=" + myList +
                '}';
    }

    /**
     * 深拷贝
     * 原理:经过了序列化与反序列化,内存地址会发送变更
     */
    public Object deepClone() {
        try {
            //输出 序列化
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);

            //输入 反序列化
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            Person copyObj = (Person) ois.readObject();

            return copyObj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

测试:

java
public static void main(String[] args) throws CloneNotSupportedException {
    ArrayList<String> myList = new ArrayList<String>() {{
        add("张三丰");
        add("赵六");
    }};
    Person person = new Person();
    person.setName("张三");
    person.setMyList(myList);

    // 浅拷贝
    Person person1 = person.clone();
    person1.setName("李四");
    person1.getMyList().add("测试1");

    // 深拷贝
    Person person2 = (Person) person.deepClone();
    person.setName("赵六");
    person2.getMyList().add("测试2");

    System.out.println(person);
    System.out.println(person1);
    System.out.println(person2);
}

结果

shell
构造方法被调用了
Person{name='赵六', myList=[张三丰, 赵六, 测试1]}
Person{name='李四', myList=[张三丰, 赵六, 测试1]}
Person{name='张三', myList=[张三丰, 赵六, 测试1, 测试2]}