WHAT

原型模式 - Prototype Pattern

定义

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototye.

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

类图

原型模式的核心是一个clone方法,通过该方法进行对象的拷贝,Java提供了Cloneable接口来标示这个对象是可拷贝的。

在JVM中具有这个标记的对象才有可能被拷贝,覆盖clone()方法来将对象编程可以被拷贝的对象。

源码模板

public class PrototypeClass implements Cloneable {
  // 覆写父类Object的clone方法
  @Override
  public PrototypeClass clone(){
    PrototypeClass prototypeClass = null;
    try {
      prototypeClass = (PrototypeClass)super.clone();
    }cache(CloneNotSupportedException e){
      	//异常处理
    }
    return prototypeClass;
  }
}

WHY

优势

  • 性能优良
    • 拷贝是内存二进制流的拷贝,比直接new一个对象的性能要好,特别在一个循环体内产生大量的对象时
  • 逃避构造函数的约束
    • 直接在内存中拷贝,所以不会执行构造函数
    • 减少了约束

劣势

  • 逃避构造函数的约束
    • 构造函数不会执行,减少了约束

HOW

使用场景

  • 资源优化场景
    • 类初始化需要消化非常多的资源
  • 性能和安全要求的场景
    • 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式
  • 一个对象多个修改者的场景
    • 需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用

注意事项

构造函数不会被执行

Object类的clone方法的原理是从内存中(堆内存)以二进制流的方式进行拷贝,重新分配一个内存块,所以不会执行构造函数。

浅拷贝和深拷贝

Object类提供的clone方法只是拷贝了本身对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝叫做浅拷贝

拷贝后两个对象之间相互独立,修改后不相互影响,这种拷贝叫做深拷贝

通过浅拷贝产生的对象,两个对象共享了一个私有变量,非常不安全。

使用clone拷贝时,内部的数组和引用对象不拷贝,原始类型(int、long、char等)和String类型都会拷贝。

String类型的对象本身没有clone方法,是通过字符串池(stringpool)在需要时才在内存中创建新的字符串,如果修改是直接修改引用地址,不会修改引用地址对象的值。

注意

使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:

  • 类的成员变量,而不是方法内的变量
  • 必须是一个可变的引用对象,而不是一个原始类型或不可变对象

clone与final两个冤家

对象的clone与对象内的final关键字是有冲突的。

final类型标记的字段,不能不修改。

如果要使用clone方法,在类的成员变量上就不要增加final关键字。

最佳实践

原型模式先产生一个包含大量公有信息的类,然后拷贝出副本,修正细节信息,建立一个完整的个性化对象。

即:

一个对象的产生可以不由零起步,直接从一个已经具备一定雏形的对象克隆,然后再修改为生产需要的对象。

TIPS

原型模式很少单独出现,一般是和工厂方法模式一起出现的。通过clone的方法创建一个对象,然后由工厂方法提供给调用者。

多线程

在多线程情况下,使用了原型模式,可能会导致多个原型对同一个“原型对象”进行修改,导致最终逻辑出现异常。

解决方法:

通过对象的复制功能来解决,将原型对象增加一个Cloneable接口。

在使用时,使用object.clone()的方法产生新的对象


《设计模式之禅》第十三章 原型模式