Prototype模式,也称原型模式,属于对象创建型模式。通过使用原型实例指定创建对象的种类,然后拷贝这些原型来创建新的对象。Prototype模式允许一个对象再创建另一个可定制的对象,即使不知道任何创建的细节,就像JavaScript中通过prototype原型公用基本的方法或属性,然后在克隆后的原型基础上添加自己的定制。
一、使用场景
1、当一个系统应该独立于它的产品创建,构成和表示时,这和抽象工厂模式的场景类似,模式之间是相互融合的。
2、当要实例化的类是在运行时刻指定时,如通过动态装载。
3、为了避免创建一个与产品类层次平行的工厂类层次时,这可以避免工厂方法模式的缺点(在创建产品的同时需要创建相应的工厂类,造成两个类层次的扩张)。
4、当一个类的实例只能有几个不同状态组合中的一种时,建立相应数目的原型并克隆他们可能比每次用合适的状态手工实例化该类更方便些,特别是该类不同实例之间只有个别属性存在差别时。
二、UML图
三、Java实现
java中对原型模式有特别支持,只要实现Cloneable这个标识性的接口,再覆盖该接口中的clone()方法,即可克隆该实现类的任何一个对象。
Java的基类java.lang.Object已经实现了Cloneable接口。
package study.patterns.prototype;
import java.util.ArrayList;
/**
* 浅拷贝:被克隆对象的所有变量都含有与原来对象相同的值,
* 而它所有的对其他对象的引用都仍然指向原来的对象。
* 深拷贝:被克隆对象的所有变量都含有与原来的对象相同的值,
* 但它所有的对其他对象的引用不再是原有的,而是指向被复制过的新对象。
* @author qbg
*/
class Family implements Cloneable{
private String name; //家庭名
private ArrayList<String> members = new ArrayList<String>();//成员
public Family(String name){
this.name = name;
}
public void addMember(String member){
members.add(member);
}
public ArrayList<String> getMembers(){
return members;
}
public void print(){
System.out.println("name:"+name);
System.out.println("members:"+members);
}
//覆盖Object基类中的clone()方法,并扩大该方法的访问权限,返回具体的Family实例
public Family clone(){
Family family = null;
try {
//浅拷贝,只复制对象属性的值,不涉及到对象引用
family = (Family)super.clone();
//深拷贝,不仅复制对象属性的值,还复制对象引用,
//复制的引用将指向被引用对象的拷贝,而不是其本身.
//family.members = (ArrayList<String>) this.members.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return family;
}
}
/**
* 原型模式:
* 通过给出一个原型对象来指明所要创建的对象的类型,
* 然后用复制这个原型对象的方法创建出更多同类型的对象。
* 原型模式允许动态的增加或减少产品类,
* 产品类不需要非得有任何事先确定的等级结构,
* 原始模型模式适用于任何的等级结构。
* 缺点是每一个类都必须配备一个克隆方法。
* @author qbg
*/
public class PrototypePattern {
public static void main(String[] args) {
Family f1 = new Family("tom family");
f1.addMember("father");
f1.addMember("mother");
System.out.println("第一代tom family:");
f1.print();
System.out.println("从第一代克隆的第二代tom family:");
Family f2 = f1.clone();
f2.addMember("son");
f2.print();
System.out.println("第二代tom family增加成员后的第一代tom family:");
f1.print();
}
}
Java中对于Cloneable的实现有两种形式,浅拷贝和深拷贝。如示例所示,现在Family实现的是浅拷贝,只将name,members的值复制到新的对象里。由于members集合中存放的是对象引用,所以新产生的拷贝对象f2和原型对象f1的members就会指向相同的引用,f2修改members的话,f1也会随着变化,运行结果如下:
第一代tom family:
name:tom family
members:[father, mother]
从第一代克隆的第二代tom family:
name:tom family
members:[father, mother, son]
第二代tom family增加成员后的第一代tom family:
name:tom family
members:[father, mother, son]
当将第36行代码的注释去掉后,Family就实现的是深拷贝。它会遍历集合,调用每个对象的clone方法,从而为每个引用开辟新的内存空间,这样f1和f2的members集合就不会相互影响,修改后运行结果如下:
第一代tom family:
name:tom family
members:[father, mother]
从第一代克隆的第二代tom family:
name:tom family
members:[father, mother, son]
第二代tom family增加成员后的第一代tom family:
name:tom family
members:[father, mother]
由于使用clone()方法来拷贝一个对象是从内存二进制流中进行IO读写,所以拷贝得到一个对象是不会执行该对象所对应类的构造函数的。类的成员变量中若有引用类型的变量(数组也是一种对象),默认的clone()并不会对其进行拷贝,需自行提供深拷贝;String类型与int、long、char等基本类型类似,默认地会被拷贝。具体实现可以参考jdk源码。
四、模式优缺点
优点:
1、对客户隐藏了具体的产品类,因此减少了客户所要了解的类的数目,并且无需改变即可使用与特定应用相关的类。
2、运行时可以增加和删除产品,只需要增加和删除相应的原型即可。
3、可以改变值以指定新对象。
4、改变结构以指定新对象。当复杂结构作为原型时,需要其实现为一个深拷贝(deep copy)。
5、减少子类的构造。原型模式不需要工厂方法模式中创建Creator类层次,所以能减少子类的构造。
6、可以用类动态配置应用。
缺点:
1、每一个Prototype的子类都必须实现Clone操作,对于已经存在的类,内部结构不支持拷贝或有循环引用的对象时,实现克隆操作很困难。