原型模式的定义是:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
这个概念看上去很抽象,其实质就是我们在编程中经常用到的对象复制,然后我们不免又要提到一个老生常谈的话题浅复制与深复制。
先看一下原型模式的UML类图吧:
其中,Prototype是一个原型的抽象类或借口,它里面有一个共有方法,叫clone。ConcretePrototype1与ConcretePrototype2是两个具体的实例,继承或实现了Prototype。这就对应了定义中用原型实例指定创建对象的种类。Client是客户端类,它与Prototype是关联的关系,即在Client类的实例中,有Prototype的对象。客户端可以通过调用Prototype的clone方法来对实现了Prototype的ConcretePrototype1或ConcretePrototype2的对象进行复制来创建新对象,这样比new会有更高的执行效率。
至于它的应有场景,我就拿《大话设计模式》中的例子吧,书中举了一个求职简历的例子,我们投公司一般都是一投好几家,简历也要准备好几份,有的还需要针对公司和职位量身定制部分内容,如果拿软件来生成简历,就比较适合使用原型模式来设计。
现在来谈谈浅复制与深复制的问题,所谓浅复制,简而言之,就是在对象中的非引用类型都会被逐位复制,引用类型复制的是引用,也就是说,在复制后的对象中,如果原有对象中含有引用类型的话,那么复制的对象与原对象指向同一个引用类型,这里的引用类型指的是数组、其他对象实例等。浅复制只做到了原对象与复制对象的相对独立;而深复制就是连引用类型也跟着复制了,原对象与复制对象完全独立。
这个例子我们用java来实现,在Java中有一个借口替我们实现好了Prototype,这个接口叫做cloneable。Sun官方的声明如下
public interface Cloneable
此类实现了 Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。
如果在没有实现 Cloneable 接口的实例上调用 Object 的 clone 方法,则会导致抛出 CloneNotSupportedException 异常。
按照惯例,实现此接口的类应该使用公共方法重写 Object.clone(它是受保护的)。请参阅 Object.clone(),以获得有关重写此方法的详细信息。
注意,此接口不 包含 clone 方法。因此,因为某个对象实现了此接口就克隆它是不可能的。即使 clone 方法是反射性调用的,也无法保证它将获得成功。
注意红字,也就是说,这个接口告诉计算机,这个类是可复制的。clone默认实现的是浅复制,下面就举一个利用原型模式和cloneable接口实现深复制的例子。在简历中一般包含有工作经验,我们就把它抽象出一个类。
代码实例如下:
import java.util.*;
//工作经验类
class Experience implements Cloneable
{
private String time;//工作时间
private String where;//工作地点
//设置工作经验
public void setExperience(String time , String where)
{
this.time = time;
this.where = where;
}
//显示工作经验
public String toString()
{
return "在"+ where + "工作过,时间为"+time+"\n";
}
//克隆方法,用于深复制
protected Object clone()throws CloneNotSupportedException
{
return super.clone();
}
}
//简历类
class Resume implements Cloneable
{
private String name;//名字
private int age;//年龄
private Experience exp;//工作经验
//构造方法
public Resume()
{
this.exp = new Experience();
}
//设置名字
public void setName(String name)
{
this.name = name;
}
//设置年龄
public void setAge(int age)
{
this.age = age;
}
//设置工作经验
public void setExp(String time , String where)
{
this.exp.setExperience(time,where);
}
//显示简历信息
public String toString()
{
return "姓名:"+this.name+" 年龄:"+ this.age+" 工作经验:"+exp.toString();
}
//克隆方法
protected Object clone()throws CloneNotSupportedException
{
Resume obj = (Resume)super.clone();
obj.name = this.name;
obj.age = age;
obj.exp = (Experience)this.exp.clone();
return obj;
}
}
public class Main
{
public static void main(String[] args)
{
try
{
//第一份简历
Resume r1 = new Resume();
r1.setName("Martin");
r1.setAge(23);
r1.setExp("2016年","Tencent");
//第二份简历
Resume r2 = (Resume)r1.clone();
r2.setExp("2017年","Baidu");
System.out.println(r1);
System.out.println(r2);
}catch(CloneNotSupportedException e)
{
e.printStackTrace();
}
}
}
运行结果如下: