原型模式
Prototype Pattern:原型模式属于GoF23种设计模式中的创建型模式的一种,提供的是一种最佳的重复创建对象的方式。
简单的理解呢,原型模式的作用就是用一个已经存在了的对象,再去创建另外一个相同的对象,并且不需要知道这个对象创建需要什么,或者说是怎么创建出来的,而是直接拿到这个新的对象,再说的简单点就是克隆了。
结构
原型模式这个模式的概念还是很简单的,可以简单的根据类图分析一下,原型模式也只有两个角色:
- 抽象原型:这个是所有具体原型对象的抽象,需要注意的就是这个角色的clone方法,这个方法也是实现原型模式的精髓所在。
- 具体原型:这个就是实际需求所需要的具体对象了,根据不同的业务有着不同的体现。
举例说明
- 原型模式的使用,在代码中更多的体现的是在克隆方面,浅克隆或者深克隆。在.NET平台中System命名空间中提供了ICloneable接口,这里的ICloneable接口就是结构类图中的抽象原型对象了,直接创建类来实现这个接口就可以直接使用原型模式了。当然JAVA中也有着相同功能的接口Coneable接口,新的类只需要实现这个接口,重写clone()方法就可以实现这个模式的运用了。
- 这样的话也不怎么好理解,可以换个更加贴近生活的例子。现在对于复印机肯定是不陌生的,其中的复印呢就是原型模式的一种体现,在你打印自己的作业啊,打印自己的论文啊,打印自己的简历等等的时候,你可以做到的就是手动建立一份原件之后就可以快速的获取无数多一模一样的出来。
注意
- 原型模式的使用,一般就是体现在克隆的方面,其次它自己本身也是一个创建型模式的一种,是一种最佳的创建很多重复对象的方法,这个可以解释一下为什么这么说,因为克隆是不会执行构造方法的,对于一些很复杂很耗时的初始化对象来说直接减去了时间和空间的消耗。
- 需要注意的是和对一个类进时实例化创建一个新对象不同的是原型模式是通过拷贝一个已经存在的对象生成新对象的,是没有对构造函数调用的。
一个小DEMO
-
场景
直接就以上面的类图为例子吧,已经可以说明这个原型模式的问题了。
-
抽象原型(因为笔者是使用的JAVA编写的,所以这里的抽象原型类需要实现Cloneable接口,不然就会报错)
神奇的是这个接口是标志接口,什么意思呢,就是说这个接口里面什么都没有,是一个空空的,啥都没有啊,但是要完成克隆的操作又必须实现这个接口,就和序列化接口一样。
package com.prototype; /** * 原型模式——抽象原型对象 * @author WQ */ public abstract class Prototype implements Cloneable{ /** * 原型模式最为重要的就是这个方法。 */ public Object clone() { Object clone = null; try { clone = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } /** * 抽象方法,用于测试是否执行了克隆 */ public abstract void show(); }
-
实现具体原型类
package com.prototype; /** * 原型模式——具体原型对象 * 就按这一个类为例子 * @author WQ */ public class PrototypeA extends Prototype{ /** * 加个字段用于测试 */ private String string; /** * 构造函数 * @param string */ public PrototypeA(String string) { this.string = string; } @Override public void show() { System.out.println("I am a PrototypeA Object!!! \n" + " This String is " + string); } }
-
测试类开始克隆啦
package com.prototype; /** * 原型模式——测试类 * @author WQ */ public class Main { public static void main(String[] args) { Prototype prototype = new PrototypeA("试试就逝世"); Prototype prototype2 = (Prototype) prototype.clone(); Prototype prototype3 = (Prototype) prototype.clone(); prototype.show(); prototype2.show(); prototype3.show(); } }
-
结果
I am a PrototypeA Object!!! This String is 试试就逝世 I am a PrototypeA Object!!! This String is 试试就逝世 I am a PrototypeA Object!!! This String is 试试就逝世
完成!!!
扩展一下
上面实现的代码是JAVA的浅克隆的方法,而不是深克隆的方法。什么意思呢?
-
浅克隆:克隆对象的时候只是克隆对象自己而已,这里的自己包括的是对象中的基本数据类型的克隆,但是对象里面的引用对象却是不考虑的,直接复制了个地址过来就不管了。这样就会导致一个问题就是我们在修改克隆对象的时候原来的对象数据也会被修改了。
package com.prototype; /** * 浅克隆 * @author WQ */ public class ShallowClone implements Cloneable{ /** * 浅克隆 */ public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } /** * 定义一个基本类型的字段 * 定义一个引用类型的字段 * 用于测试 */ private String string; private Demo demo; public ShallowClone(String string, Demo demo) { super(); this.string = string; this.demo = demo; } public Demo getDemo() { return demo; } public void setDemo(Demo demo) { this.demo = demo; } @Override public String toString() { return "ShallowClone [string=" + string + ", demo=" + demo.toString() + "]"; } /** * 测试方法 * @param args */ public static void main(String[] args) { Demo demo = new Demo("demo", 12); ShallowClone shallowClone = new ShallowClone("测试",demo); System.out.println("原来的shallowClone :" + shallowClone.toString()); ShallowClone shallowClone2 = (ShallowClone) shallowClone.clone(); System.out.println("克隆的shallowClone2 :" + shallowClone2.toString()); //这里注意是对shallowClone2这个对象的修改 Demo demo2 = shallowClone2.getDemo(); demo2.setString("demo2"); demo2.setData(24); System.out.println("修改的shallowClone :" + shallowClone.toString()); System.out.println("修改的shallowClone2 :" + shallowClone2.toString()); } } /** * 用于测试的引用类型的对象 */ class Demo{ private String string; private int data; public Demo(String string, int data) { super(); this.string = string; this.data = data; } public void setString(String string) { this.string = string; } public void setData(int data) { this.data = data; } @Override public String toString() { return "Demo [string=" + string + ", data=" + data + "]"; } }
得到的结果
原来的shallowClone :ShallowClone [string=测试, demo=Demo [string=demo, data=12]] 克隆的shallowClone2 :ShallowClone [string=测试, demo=Demo [string=demo, data=12]] 修改的shallowClone :ShallowClone [string=测试, demo=Demo [string=demo2, data=24]] 修改的shallowClone2 :ShallowClone [string=测试, demo=Demo [string=demo2, data=24]]
可以看见对于shallowClone2对象的修改直接应用到了shallowClone对象上面,这是很不应该的,所以对于引用类型的对象,我们需要使用深克隆。
-
深克隆:深克隆就比浅克隆多了一条,就是不仅克隆自己本身,还克隆自己所包含的引用指向的所有对象。
//只需要修改之前的代码即可,主要就是clone方法的实现 public Object clone() { try { DeepClone deepClone = (DeepClone) super.clone(); //深拷贝需要添加的代码 deepClone.setDemo((Demo)deepClone.getDemo().clone()); return deepClone; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; }
这里需要注意的是Demo对象也需要实现Cloneable接口并且有自己的clone()方法的实现。
于是结果就正常了
原来的deepClone :DeepClone [string=测试, demo=Demo [string=demo, data=12]] 克隆的deepClone2 :DeepClone [string=测试, demo=Demo [string=demo, data=12]] 修改的deepClone :DeepClone [string=测试, demo=Demo [string=demo, data=12]] 修改的deepClone2 :DeepClone [string=测试, demo=Demo [string=demo2, data=24]]
-
需要注意的就是引用类型这一种,对于基本数据类型来说可以直接看成就是深克隆。