设计模式 - 原型模式(Prototype)

定义

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象

类型

创建类模式

适用性

当一个系统应该独立于它的产品创建、构成和表示时, 要使用 Prototype 模式;以及当要实例化的类是在运行时刻指定时,例如,通过动态装载;或者为了创建一个与产品类层次平行的工厂类层次时;或者当一个类的实例只能由几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

类图

原型模式类图

参与者

  1. 原型(Prototype): 声明一个克隆自身的接口;
  2. 具体原型(Concrete Prototype): 实现一个克隆自身的操作;
  3. 客户端(Client): 让一个原型克隆自身从而创建一个新的对象。

原型模式的优点

  1. 如果创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
  2. 可以使用深克隆保持对象的状态。
  3. 原型模式提供了简化的创建结构。

原型模式的缺点

  1. 实现深克隆可能相对比较复杂。
  2. 需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。

原型模式的使用场景

  1. 对于创建新对象成本较大的情况,可以利用已有的对象进行复制来获得。
  2. 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式来应用。相反,如果对象的状态变化很大,或者对象占用的内存很大,那么采用状态模式会比原型模式更好。
  3. 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。

原型模式的注意事项

  1. Java 中,使用原型模式复制对象不会调用类的构造方法,因为对象的复制是通过调用 Object 类的 clone() 方法来完成的。
  2. 不但构造方法中的代码不会执行,访问权限对原型模式也无效。单例模式中,只要将构造方法的访问权限设置为 private 就可以实现单例。但是 clone() 方法无视构造方法的权限,所以某种意义上单例模式与原型模式是冲突的。
  3. 深拷贝与浅拷贝。Object 类的 clone() 方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。(Java 中,会发生深拷贝的有 8 种基本类型以及他们的封装类型,另外还有 String 类型,其余的都是浅拷贝)

代码实现

具体原型

@Data
public class User implements Cloneable {
    private String userName;
    private String password;

    @Override
    public Object clone() {
        User user = null;
        try {
            user = (User) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }
}

客户端

public static void main(String...args) {
    User user1 = new User();
    user1.setUserName("user1");
    user1.setPassword("123456");

    User user2 = (User)user1.clone();

    System.out.println("user1 == user2 " + (user1 == user2));
    System.out.println("user1.getClass() == user2.getClass() " + (user1.getClass() == user2.getClass()));
    System.out.println("user1.equals(user2) " + (user1.equals(user2)));
}

参考代码

https://github.com/xueyufish/design-pattern-java/tree/master/prototype