原型模式是通过已有的模板来客隆对象的一种创建型设计模式。原型就是一种模板。主要适用于通过new
关键字来创建对象比较复杂时的场景。
定义
用原型模板指定创建对象的种类,并通过拷贝这些原型对象创建新的对象。
场景
- 当一个对象的构造,利用
new
和通过clone
的效率差别很大时,可以考虑使用原型模式。特别是在循环体内部,效率的提升会更加明显。 - 当需要对原始实例进行写保护的时候,可以通过克隆一份副本实例提供给使用者。
特点
- 降低创建某些对象的资源消耗
- 对象克隆中保护性拷贝和非保护性拷贝
- 通过
Cloneable
接口创建对象时,不会执行类的构造方法
保护性拷贝(深拷贝)
在对象的拷贝时,针对引用类型不是直接通过引用指向来实现,而是遍历引用类型,对引用类型再执行clone
方法,也可以理解为对于引用类型,会重新创建新的实例。
保护性拷贝需要确保的是,修改拷贝对象的所有结构,对原型实例不会产生任何影响
非保护性拷贝(浅拷贝)
在对象的拷贝时,只是通过对象的clone
方法生成副本对象,如果原型对象有引用类型的变量,在副本对象中修改引用类型变量,也会对原型变量造成影响。这很好理解,因为原型对象和副本对象的引用都指向了同一块内存空间。
非保护性拷贝需要注意的是,在原型对象中有引用类型的变量时,需要确定副本实例在系统中是否对原型实例构成了威胁
源码分析
下面是一份重写Object
类的````clone``方法实现的原型模式代码
public class PrototypeUser {
private String name;
private int age;
private Address address;
@Override protected Object clone() throws CloneNotSupportedException {
PrototypeUser clone = (PrototypeUser) super.clone();
clone.address = (Address) address.clone();
return clone;
}
public static class Address {
private String cuty;
private String street;
@Override protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
}
以上代码中可以看出,针对基础数据类型age
和String
类型的克隆,是直接调用了Object
类的克隆方法,而针对引用类型的变量address
则调用了address
自身的克隆方法。这样做就实现了保护性拷贝的操作。
当然并不是所有的原型模式都依赖Object
的clone
方法。
原型模式主要解决的是创建复杂对象的问题。看以下代码片段
public class Prototy {
private String name;
private List<String> todos = new ArrayList<>();
public Prototy copy() {
Prototy p = new Prototy();
p.name = name;
p.todos = new ArrayList<>();
if (todos.size() > 0) {
p.todos.addAll(todos);
}
return p;
}
}
上述代码片段中的copy
方法,也是利用了原始的对象生成内部构造相同的新对象。
这样做简化了对象创建流程,隐藏了变量赋值过程,而且copy
生成的对象与原型对象是完全独立的。不存在浅拷贝引发的问题。在特定环境下,可以用copy
出的对象代替原始对象,这样子对原始对象的数据还可以进行隐藏保护,不会被随意篡改。
总结
原型模式本质上是对象的拷贝,在这里面需要注意深拷贝和浅拷贝造成的不同影响。原型模式的主要用途是削减复杂对象创建过程的资源消耗;还有就是对实例的数据保护,通过生成副本对象,来对原始对象的数据进行写保护。
原型模式的最大优点就是更加节省的创建对象,特别是在循环体内部,这种优化是很高效的。当然在利用Object
的clone
方法克隆对象时,不会执行类的构造方法,这也是需要注意的,特别是类在构造器中进行了一些重要的操作。