设计模式之原型模式

定义

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。可以简单类比于孙悟空用毫毛变化出很多和自己一模一样的小猴兵。

结构

  • Prototype,原型接口,定义了克隆自身的方法。
  • ConcretePrototype,具体原型类,实现了原型接口。
  • Client,使用原型的客户端。

简单实现

原型接口

public interface Prototype {

  /**
   * 克隆自身
   */
  Prototype clone();

}

具体原型类

public class ConcretePrototype implements Prototype {

  private String name;
  private int age;

  public ConcretePrototype(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  @Override
  public Prototype clone() {
    return new ConcretePrototype(this.name, this.age);
  }

  @Override
  public String toString() {
    return this.name + "," + age;
  }
}

客户端

public class Client {

  public static void main(String[] args) {
    //创建原型对象
    ConcretePrototype prototype = new ConcretePrototype("lisi", 24);
    System.out.println(prototype);//lisi,24
    //根据原型对象克隆出新的对象
    ConcretePrototype clonedPrototype = (ConcretePrototype) prototype.clone();
    System.out.println(clonedPrototype);//lisi,24
    System.out.println(prototype == clonedPrototype);//false
    System.out.println(prototype.getName() == clonedPrototype.getName());//true
  }

}

java本身提供了 Cloneable 接口,我们只需要重写 Object 中的 clone()方法就可以了

public class ConcretePrototype implements Cloneable {

  private String name;
  private int age;

  public ConcretePrototype(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  @Override
  public Object clone() {
    try {
      return super.clone();
    } catch (CloneNotSupportedException e) {
      throw new InternalError(e);
    }
  }

  @Override
  public String toString() {
    return this.name + "," + age;
  }
}

客户端代码和上面一样,Cloneable 接口下没有定义方法,只是作为一个声明,如果重写了clone()方法但没有实现此接口,就会抛出 CloneNotSupportedException 异常。

原型模式在JDK的实现

JDK中ArrayList

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    /**
     * Returns a shallow copy of this {@code ArrayList} instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this {@code ArrayList} instance
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
}

客户端使用

import java.util.ArrayList;

public class TestArrayListClone2 {

  public static void main(String[] args) {
    ArrayList<String> names = new ArrayList<>();
    names.add("lisi");
    names.add("lisi2");
    System.out.println(names);
    ArrayList<String> clonedNames = (ArrayList<String>) names.clone();
    System.out.println(clonedNames);
  }

}

其他类似的LinkedList,HashMap也都实现了 Cloneable 接口。

浅克隆和深克隆

  • 浅克隆:只负责克隆基本数据类型的数据
  • 深克隆:基本数据类型和引用类型的数据都会被克隆,如果属性中还包含引用类型的属性,会一直递归的克隆下去。

ArrayList等类中的克隆实现都是浅克隆,一般通过序列化方式来实现深克隆。

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;

public class TestArrayListClone {

  public static void main(String[] args) {
    testShallowCopy();
    testDeepCopy();
  }

  private static void testShallowCopy() {
    ArrayList<User> users = new ArrayList<>();
    users.add(new User("lisi"));
    ArrayList<User> clonedUsers = (ArrayList<User>) users.clone();
    System.out.println("====浅克隆====");
    System.out.println(clonedUsers.size() == users.size());
    System.out.println(clonedUsers == users);
    System.out.println(clonedUsers.get(0) == users.get(0));
  }

  private static void testDeepCopy() {
    ArrayList<User> users = new ArrayList<>();
    users.add(new User("lisi"));
    ArrayList<User> clonedUsers = (ArrayList<User>) deepCopy(users);
    System.out.println("====深克隆====");
    System.out.println(clonedUsers.size() == users.size());
    System.out.println(clonedUsers == users);
    System.out.println(clonedUsers.get(0) == users.get(0));
  }

  private static Object deepCopy(Object obj) {
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      oos.writeObject(obj);
      byte[] data = baos.toByteArray();
      ByteArrayInputStream bais = new ByteArrayInputStream(data);
      ObjectInputStream bis = new ObjectInputStream(bais);
      return bis.readObject();
    } catch (IOException | ClassNotFoundException e) {
      e.printStackTrace();
    }
    return null;
  }

  private static class User implements Serializable {

    public User(String name) {
      this.name = name;
    }

    String name;
  }
}

输出结果为

====浅克隆====
true
false
true
====深克隆====
true
false
false

可以看到,浅克隆的结果中User对象是同一个,深克隆结果中User对象是两个不同的对象。

总结

优点

  1. 当对象实例比较复杂时,使用原型模式可以简化创建新对象的过程,并且 java 自带的克隆实现是基于内存中二进制流的复制,相比于直接 new 一个对象,性能上更加优良。

缺点

  1. clone方法位于类的内部,当对已有类进行改造的时候,可能也需要修改克隆方法,违背了开闭原则。
  2. 在不使用序列化的情况下,为了支持深克隆,当对象之间存在多重嵌套引用关系时,每一层对象都必须支持深克隆,实现起来可能比较麻烦。

本质

原型模式的本质是克隆生成对象,克隆是手段,生成对象是目的。

使用场景

  1. 当创建新对象成本较大时(例如初始化需要较长的时间,占用太多的CPU资源或者网络资源),这种情况下可以考虑使用原型模式来创建新对象。

参考

大战设计模式【23】—— 原型模式
设计模式的征途—5.原型(Prototype)模式
研磨设计模式-书籍

posted @ 2021-08-13 21:26  strongmore  阅读(48)  评论(0编辑  收藏  举报