原型模式(Prototype Pattern):使用原型实例指定创建对象的类型,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。 |
原型模式结构
原型模式的工作原理很简单:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。由于在软件系统中我们经常会遇到需要创建多个相同或者相似对象的情况,因此原型模式在实际开发中的使用频率还是非常高的。
需要注意的是通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,通常对克隆所产生的对象进行修改,不会对原型对象造成任何影响,每一个克隆对象都是相互独立的。通过不同的修改方式可以得到一系列相似但不完全相同的对象。
原型模式的结构图如下所示;

在原型模式结构图中包含如下几个角色:
● Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。
● ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
● Client(客户端类):让一个原型对象克隆自身从而创建一个新的对象,在客户端类中只需要直接实例化创建一个原型对象,再通过调用该对象的克隆方法,即可得到多个相同的对象。由于客户端类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。
原型模式实现
解决方案一 原型模式的实现 原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件: ● 实现Cloneable接口。Java语言中有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone()方法。在Java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。 ● 重写Object类中的clone()方法。Java语言中所有类的父类都是Object类,Object类中有一个clone()方法,作用是返回对象的一个拷贝,但是其作用域是protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。 原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即实现了原型模式。在实际开发中,原型模式很少单独出现。经常与其他模式混用,其原型类Prototype通常用抽象类来替代。关键代码如下所示; public interface Prototype extends Cloneable {
public Prototype clone();
}
public class ConcretePrototypeA implements Prototype {
public Prototype clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("Not Support Cloneable");
}
return (Prototype)object;
}
}
public class ConcretePrototypeB implements Prototype {
public Prototype clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("Not Support Cloneable");
}
return (Prototype)object;
}
}
public class Client {
Prototype concretePrototypeA = new ConcretePrototypeA();
Prototype cloneConcretePrototypeA = concretePrototypeA.clone();
Prototype concretePrototypeB = new ConcretePrototypeB();
Prototype cloneConcretePrototypeB = concretePrototypeB.clone();
public void operation(){
// 客户端进行的操作
}
} 如果需要增加一种新的具体原型类ConcretePrototypeC,只需要将ConcretePrototypeC作为Prototype的实现类,实现Prototype接口,重写clone()方法即可,无需修改原有类库的源代码,有利于系统的扩展,符合开闭原则。 当然,抽象原型类也可以是具体抽象类:当系统只有一个具体原型类ConcretePrototype的时候,就没有必要设计抽象原型类Prototype,从而简化原型模式的结构,其结构图如下所示;  关键实现代码如下所示; public class ConcretePrototype implements Cloneable {
public ConcretePrototype clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("Not Support Cloneable");
}
return (ConcretePrototype)object;
}
}
public class Client {
// Object相当于抽象原型类
Object concretePrototype = new ConcretePrototype();
Object cloneConcretePrototype = concretePrototype.clone();
// ConcretePrototype concretePrototype = new ConcretePrototype();
// ConcretePrototype cloneConcretePrototype = concretePrototype.clone();
}
|
讲到原型模式,我们不得不区分一下两种不同的克隆方法,浅克隆(ShallowClone)和深克隆(DeepClone)。在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。
解决方案二 浅克隆原型模式的实现 在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制,如下图所示;  理解了浅克隆的概念,我们现在利用浅克隆实现原型模式,为了简化原型模式的设计,我们假设系统只有一个具体实现类,其结构图如下所示;  关键实现代码如下所示; public class Aggregation {
// setter()方法
// getter()方法
public void operation(){
}
}
public class ConcretePrototype implements Cloneable {
private Aggregation aggregation;
public void setAggregation(Aggregation aggregation) {
this.aggregation = aggregation;
}
public Aggregation getAggregation() {
return this.aggregation;
}
// 使用clone()方法实现浅克隆
public ConcretePrototype clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("Not support cloneable");
}
return (ConcretePrototype)object;
}
}
public class Client {
ConcretePrototype concretePrototype = new ConcretePrototype(); //创建原型对象
Aggregation aggregation = new Aggregation(); //创建聚合对象
concretePrototype.setAggregation(aggregation); //将聚合对象添加到原型对象中
ConcretePrototype cloneConcretePrototype = concretePrototype.clone(); //调用克隆方法创建克隆原型对象
System.out.println("原型对象与克隆对象是否相同? " + (concretePrototype == cloneConcretePrototype));
System.out.println("原型对象与克隆对象的引用类型成员变量是否相同? " +
(concretePrototype.getAggregation() == cloneConcretePrototype.getAggregation()));
} 由于使用的是浅克隆技术,因此原型对象复制成功,通过“==”比较原型对象和克隆对象的内存地址时输出false;但是比较引用类型成员变量(aggregation对象)的内存地址时输出true,说明它们在内存中是同一个对象。 |
解决方案三 深克隆原型模式的实现 在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制,如下图所示;  在Java语言中,如果需要实现深克隆,可以通过序列化(Serialization)等方式来实现。序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。下面我们使用深克隆技术来实现ConcretePrototype和Aggregation对象的复制,由于要将ConcretePrototype和Aggregation对象都写入流中,因此两个类均需要实现Serializable接口,其结构如下图所示;  具体原型类ConcretePrototype不再使用Java自带的克隆机制,而是通过序列化来实现对象的深克隆,关键代码如下所示; public class Aggregation implements Serializable {
// setter()方法
// getter()方法
public void operation() {
}
}
public class ConcretePrototype implements Serializable {
private Aggregation aggregation;
public void setAggregation(Aggregation aggregation) {
this.aggregation = aggregation;
}
public Aggregation getAggregation() {
return this.aggregation;
}
// 使用序列化技术实现深克隆
public ConcretePrototype deepClone() throws IOException, ClassNotFoundException, OptionalDataException {
// 将对象写入流中
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
// 将对象从流中取出
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (WeeklyLog)objectInputStream.readObject();
}
}
public class Client {
ConcretePrototype concretePrototype = new ConcretePrototype(); //创建原型对象
Aggregation aggregation = new Aggregation(); //创建聚合对象
concretePrototype.setAggregation(aggregation); //将聚合对象添加到原型对象中
try {
ConcretePrototype cloneConcretePrototype = concretePrototype.deepClone(); //调用克隆方法创建克隆原型对象
} catch(Exception e) {
System.out.println("Clone Failure");
}
System.out.println("原型对象与克隆对象是否相同? " + (concretePrototype == cloneConcretePrototype));
System.out.println("原型对象与克隆对象的引用类型成员变量是否相同? " +
(concretePrototype.getAggregation() == cloneConcretePrototype.getAggregation()));
} 从输出结果可以看出,由于使用了深克隆技术,引用类型成员变量(aggregation对象)也得以复制,因此用“==”比较原型对象和克隆对象的aggregation对象时输出结果均为false。深克隆技术实现了原型对象和克隆对象的完全独立,对任意克隆对象的修改都不会给其他对象产生影响,是一种更为理想的克隆实现方式。 |
解决方案四 带原型管理器的原型模式的实现 原型管理器(Prototype Manager)是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。在原型管理器中针对抽象原型类进行编程,以便扩展。其结构图如下所示;  为了代码的可读性,我们简化了每个类的设计,其关键实现代码如下所示; public interface Prototype extends Cloneable {
public Prototype clone();
}
public class ConcretePrototypeA implements Prototype {
public Prototype clone() {
Prototype object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("Not Support Cloneable");
}
return (Prototype)object;
}
}
public class ConcretePrototypeB implements Prototype {
public Prototype clone() {
Prototype object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("Not Support Cloneable");
}
return (Prototype)object;
}
}
// 原型管理器(使用饿汉式单例实现)
public class PrototypeManager {
//定义一个Hashtable,用于存储原型对象
private Hashtable hashtable = new Hashtable();
private static PrototypeManager prototypeManager = new PrototypeManager();
// 为Hashtable增加ConcretePrototype对象
private PrototypeManager() {
hashtable.put("CPA", new ConcretePrototypeA());
hashtable.put("CPB", new ConcretePrototypeB());
}
// 增加新的原型对象类型
public void addPrototype(String key, Prototype prototype) {
hashtable.put(key, prototype);
}
// 通过浅克隆获取对应原型对象类型的克隆对象
public Prototype getPrototype(String key) {
return ((Prototype)hashtable.get(key)).clone();
}
// 返回当前的原型管理器对象
public static PrototypeManager getPrototypeManager() {
return prototypeManager;
}
}
public class Client {
// 获取原型管理器对象
PrototypeManager prototypeManager = PrototypeManager.getPrototypeManager();
Prototype concretePrototypeA1, concretePrototypeA2, concretePrototypeB1, concretePrototypeB2;
concretePrototypeA1 = prototypeManager.getPrototype("CPA");
concretePrototypeA2 = prototypeManager.getPrototype("CPA");
System.out.println(concretePrototypeA1 == concretePrototypeA2);
concretePrototypeB1 = prototypeManager.getPrototype("CPB");
concretePrototypeB2 = prototypeManager.getPrototype("CPB");
System.out.println(concretePrototypeB1 == concretePrototypeB2);
} 在PrototypeManager中定义了一个Hashtable类型的集合对象,使用“键值对”来存储原型对象,客户端可以通过Key(如“CPA”或“CPB”)来获取对应原型对象的克隆对象。PrototypeManager类提供了类似工厂方法的getPrototype()方法用于返回一个克隆对象。在这里,我们将PrototypeManager设计为单例类,使用饿汉式单例实现,确保系统中有且仅有一个PrototypeManager对象,有利于节省系统资源,并可以更好地对原型管理器对象进行控制。 |
适用场景
在以下情况下可以考虑使用原型模式:
(1) 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有 对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
(2) 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
(3) 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
效果
原型模式作为一种快速创建大量相同或相似对象的方式,在软件开发中应用较为广泛,很多软件提供的复制(Ctrl + C)和粘贴(Ctrl + V)操作就是原型模式的典型应用,下面对该模式的使用效果和适用情况进行简单的总结。
1.主要优点
原型模式的主要优点如下:
(1) 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
(2) 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。
(3) 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
(4) 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。
2.主要缺点
原型模式的主要缺点如下:
(1) 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
(2) 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。
应用实例
设计问题:COS系统是一款网上订餐软件,Notification类负责构建通知,通知有固定的格式(title-通知标题,content-通知内容,footer-公司logo/落款),业务中有不同的通知类型,如订单生成通知,订餐配送通知,订单完成通知等,在未来随着系统业务的不断增加,可能会增加新的通知类型,如何解决该设计问题? 解决方案:在COS系统里,需要创建大量的通知类型的对象,而且每个通知类对象的状态改变非常小,比如footer、title等可能都不需要改变,因此考虑到原型模式,利用已有的对象复制出新的对象,减少创建新对象的成本,改善软件运行效率。其类结构图如下所示;  关键代码实现如下所示; public class Notification implements Cloneable {
private String title;
private String content;
private Footer footer;
// setter() getter()方法
public Notification clone() {
Notification object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("Not Support Cloneable");
}
return (Notification)object;
}
}
public class Client {
Notification notification = new Notification();
Notification cloneNotification = notification.clone();
}
|