Skip to content

原型模式

定义:原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。调用者不需要关心创建细节,不调用构造方法

适用场景:

  1. 类初始化时消耗的资源较多
  2. new关键字产生的一个对象需要非常繁琐的过程(字段较多)
  3. 循环体中生产大量对象

优点:

  1. 性能优秀,JDK 自带的原型模式是基于 内存二进制流 的拷贝,比直接 new 一个对象性能上提升了许多

  2. 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建过程

缺点:

  1. 必须配备 clone 方法和实现 Cloneable 接口

  2. 深克隆、浅克隆要运用得当

浅克隆写法

java
@Data
public class ConcretePrototype implements Cloneable {
    private Integer id;
    private int age;
    private String name;
    private Gender gender;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}
java
@Data
public class Gender {
    private Integer id;
    private String name;
}
java
public class Client {
    public static void main(String[] args) {
        Gender gender = new Gender();
        gender.setId(0);
        gender.setName("女性");
        List<String> list = new ArrayList<>();
        list.add("唱");
        list.add("跳");
        list.add("rap");

        ConcretePrototype prototype = new ConcretePrototype();

        prototype.setId(200);
        prototype.setAge(18);
        prototype.setName("张三");
        prototype.setGender(gender);
        prototype.setHobbies(list);

        ConcretePrototype cloneType = prototype.clone();
        Integer id = cloneType.getId();
        id = id + 300;
        cloneType.setId(id);
        cloneType.setAge(19);
        cloneType.setName("李四");
        final Gender gender1 = cloneType.getGender();
        gender1.setId(1);
        gender1.setName("男性");
        cloneType.getHobbies().add("打篮球");

        System.out.println(prototype);
        System.out.println(cloneType);
    }
}

ConcretePrototype(id=200, age=18, name=张三, gender=Gender(id=1, name=男性), hobbies=[唱, 跳, rap, 打篮球])

ConcretePrototype(id=500, age=19, name=李四, gender=Gender(id=1, name=男性), hobbies=[唱, 跳, rap, 打篮球])

浅克隆的特点

  • 基本数据类型及其包装类型和 String 克隆后修改不影响原来的对象
  • 引用数据类型修改后会影响原来的对象,存在被覆盖的风险,克隆时只是拷贝了引用的地址

深克隆写法

使用序列化和反序列化的方法来克隆对象,这样会把引用类型的值也拷贝一份

java
@Data
public class ConcretePrototype implements Cloneable, Serializable {
    private Integer id;
    private int age;
    private String name;
    private Gender gender;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone() {
        try {
            ConcretePrototype clone = (ConcretePrototype) super.clone();
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

    public ConcretePrototype deepClone() {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(this);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            return (ConcretePrototype) objectInputStream.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
java
public class Client {
    public static void main(String[] args) {
        Gender gender = new Gender();
        gender.setId(0);
        gender.setName("女性");
        List<String> list = new ArrayList<>();
        list.add("唱");
        list.add("跳");
        list.add("rap");

        ConcretePrototype prototype = new ConcretePrototype();

        prototype.setId(200);
        prototype.setAge(18);
        prototype.setName("张三");
        prototype.setGender(gender);
        prototype.setHobbies(list);

        ConcretePrototype cloneType = prototype.deepClone();
        Integer id = cloneType.getId();
        id = id + 300;
        cloneType.setId(id);
        cloneType.setAge(19);
        cloneType.setName("李四");
        final Gender gender1 = cloneType.getGender();
        gender1.setId(1);
        gender1.setName("男性");
        cloneType.getHobbies().add("打篮球");

        System.out.println(prototype);
        System.out.println(cloneType);
    }
}

ConcretePrototype(id=200, age=18, name=张三, gender=Gender(id=0, name=女性), hobbies=[唱, 跳, rap])

ConcretePrototype(id=500, age=19, name=李四, gender=Gender(id=1, name=男性), hobbies=[唱, 跳, rap, 打篮球])

通过把对象转成字节流再转成对象后,对象中的所有属性包括引用类型都被彻底复制了一份新的出来了,修改后不影响原来对象

原型模式在源码中的体现

ArrayList

ArrayList 中的 clone 方法

先调用父类的 clone 方法创建了一个新的 ArrayList 对象

然后通过 Arrays 中的 copyOf 方法把原来数组中的内容重新创建并返回给新的 ArrayList

HashMap

HashMap 中的 clone 方法

先调用父类中的 clone 方法创建了一个新的 HashMap 对象

然后对新的 HashMap 对象重新初始化并把原有的内容重新存放到新的 HashMap 对象中