《设计模式》之原型模式
定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
类型:创建类模式
类图:
客户(Client)角色:使用原型对象的客户程序
抽象原型(Prototype)角色:规定了具体原型对象必须实现的接口(如果要提供深拷贝,则必须具有实现clone的规定)
具体原型(ConcretePrototype):从抽象原型派生而来,是客户程序使用的对象,即被复制的对象。此角色需要实现抽象原型角色所要求的接口。
原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。
Prototype类需要具备以下两个条件:
实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。
原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。
class Prototype implements Cloneable {
public Prototype clone(){
Prototype prototype = null;
try{
prototype = (Prototype)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return prototype;
}
}
class ConcretePrototype extends Prototype{
public void show(){
System.out.println("原型模式实现类");
}
}
public class Client {
public static void main(String[] args){
ConcretePrototype cp = new ConcretePrototype();
for(int i=0; i< 10; i++){
ConcretePrototype clonecp = (ConcretePrototype)cp.clone();
clonecp.show();
}
}
}
原型模式的优点及适用场景
- 使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。
因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。
原型模式的注意事项
使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。
深拷贝与浅拷贝。深拷贝就是对象引用和对象一起给复制了一遍,实现了这个物理上的完全隔离,而浅拷贝则是简单的复制了对象的引用,实质上还是指向同一个对象,基本数据类型和string类的拷贝都是深拷贝;Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。例如:
public class Prototype implements Cloneable {
private ArrayList list = new ArrayList();
public Prototype clone(){
Prototype prototype = null;
try{
prototype = (Prototype)super.clone();
prototype.list = (ArrayList) this.list.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return prototype;
}
}
由于ArrayList不是基本类型,所以成员变量list,不会被拷贝,需要我们自己实现深拷贝,幸运的是java提供的大部分的容器类都实现了Cloneable接口。所以实现深拷贝并不是特别困难。
深拷贝与浅拷贝问题中,会发生深拷贝的有java中的8中基本类型以及他们的封装类型,另外还有String类型。其余的都是浅拷贝。
关于String类型是否是深拷贝将在下一篇博文中详细介绍。
适用性
原型模式的主要思想是基于现有的对象克隆一个新的对象出来,一般是有对象的内部提供克隆的方法,通过该方法返回一个对象的副本,这种创建对象的方式,相比我们之前说的几类创建型模式还是有区别的,之前的讲述的工厂模式与抽象工厂都是通过工厂封装具体的new操作的过程,返回一个新的对象,有的时候我们通过这样的创建工厂创建对象不值得,特别是以下的几个场景的时候,可能使用原型模式更简单也效率更高。
• 1)当一个系统应该独立于它的产品创建、构成和表示时,要使用 Prototype模式
• 2)当要实例化的类是在运行时刻指定时,例如,通过动态装载;
• 3)为了避免创建一个与产品类层次平行的工厂类层次时
• 4)当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。(也就是当我们在处理一些对象比较简单,并且对象之间的区别很小,可能只是很固定的几个属性不同的时候,可能我们使用原型模式更合适)。
创建类模式总结
到这里为止创建类模式就结束了。
创建类模式有以下几种
- 单例模式:只有一个对象实例
- 简单工厂模式:一个工厂生产所有的对象实例。
- 工厂方法模式:一个工厂值生产一个对象实例,“1对1”的关系
- 抽象工厂模式:生产一组相关或相互依赖的对象实例,“1对多”的关系
- 建造者模式:按照规定的流程来生产更加复杂的实例对象。
- 原型模式:克隆一个对象,用于得到一个对象的拷贝
为什么需要创建性模式
- 1、首先,在编程中,对象的创建通常是一件比较复杂的事,因为,为了达到降低耦合的目的,我们通常采用面向抽象编程的方式,对象间的关系不会硬编码到类中,而是等到调用的时候再进行组装,这样虽然降低了对象间的耦合,提高了对象复用的可能,但在一定程度上将组装类的任务都交给了最终调用的客户端程序,大大增加了客户端程序的复杂度。采用创建类模式的优点之一就是将组装对象的过程封装到一个单独的类中,这样,既不会增加对象间的耦合,又可以最大限度的减小客户端的负担。
- 其次,使用普通的方式创建对象,一般都是返回一个具体的对象,即所谓的面向实现编程,这与设计模式原则是相违背的。采用创建类模式则可以实现面向抽象编程。客户端要求的只是一个抽象的类型,具体返回什么样的对象,由创建者来决定。
- 再次,可以对创建对象的过程进行优化,客户端关注的只是得到对象,对对象的创建过程则不关心,因此,创建者可以对创建的过程进行优化,例如在特定条件下,如果使用单例模式或者是使用原型模式,都可以优化系统的性能。
总结
所有的创建类模式本质上都是对对象的创建过程进行封装。
参考资料
1、http://blog.csdn.net/zhengzhb/article/details/7393528
2、http://blog.csdn.net/hguisu/article/details/7518947