原型模式(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。(克隆一个已有的类的实例)。
应用场景
应用Clone的场景应该说非常多,理想情况下我当然希望任何类都能Clone, 需要的时候就能Clone一份一模一样的出来。
原型模式的适用场景:(摘录自《设计模式迷你手册》)
1、当要实例化的类是在运行时刻指定时,例如,通过动态装载;
2、为了避免创建一个与产品类层次平行的工厂类层次时;
3、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
结构模式
无非就是为类做一个clone()函数,使使用该类的调用者,能调用该方法克隆该类的实例。
只要为需要Clone能力 的类定义一个Clone方法就行。当然,一般,主流的程序语言框架都已经定义了通用的Clone接口(当然也可以自己定义),继承并实现该接口和方法就好。
在Java中,原型模式可以很简单地实现,只要实现Cloneable这个标识性的接口,再覆盖该接口中的clone()方法,即可“克隆”该实现类的任何一个对象。“克隆”的意思大家都明白,就是原封不动的复制。
实例展现
由于Java中的最基类Object类中已经实现了Cloneable接口,故我们下面的代码例子中的会看不到上面类图中Prototype这个抽象类的影子。
具体代码如下:
//原型01,实现Cloneable接口并覆盖clone()方法
class ConcretePrototype01 implements Cloneable {
private String name;
public ConcretePrototype01(String name) {
this.name = name;
}
public void getName() {
System.out.println(name);
}
//覆盖Object基类中的clone()方法,并扩大该方法的访问权限,具体化返回本类型
public ConcretePrototype01 clone() {
ConcretePrototype01 self = null;
try {
self = (ConcretePrototype01) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return self;
}
}
//测试类
public class Client {
public static void main(String[] args) {
ConcretePrototype01 prototype01 = new ConcretePrototype01("蚂蚁 ...");
prototype01.getName();
//通过clone获得一个拷贝
ConcretePrototype01 fromClone01 = prototype01.clone();
fromClone01.getName();
}
}
测试结果:
蚂蚁 ... 蚂蚁 ... |
在Java中有个浅拷贝和深拷贝之分,下面再给出个代码例子。
//原型02,成员变量中包含引用变量,得用深拷贝
class ConcretePrototype02 implements Cloneable {
private String name;
private ArrayList<String> nameList = new ArrayList<String>();
public ConcretePrototype02(String name) {
this.name = name;
this.nameList.add(this.name);
}
//添加nameList中的对象
public void setName(String name) {
this.nameList.add(name);
}
public ArrayList<String> getNameList() {
return this.nameList;
}
//覆盖Object基类中的clone()方法,并扩大该方法的访问权限,具体化返回本类型
public ConcretePrototype02 clone() {
ConcretePrototype02 self = null;
try {
self = (ConcretePrototype02) super.clone();
//以下这句是实现深拷贝的关键
// self.nameList = (ArrayList<String>) this.nameList.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return self;
}
}
//测试类
public class Client {
public static void main(String[] args) {
ConcretePrototype02 prototype02 = new ConcretePrototype02("蚂蚁 ...");
System.out.println(prototype02.getNameList());
//通过clone获得一个拷贝
ConcretePrototype02 fromClone02 = prototype02.clone();
fromClone02.setName("小蚂蚁 ...");
System.out.println(fromClone02.getNameList());
System.out.println(prototype02.getNameList());
}
}
测试结果:
拷贝之前的原型: [蚂蚁 ...] 拷贝得到的对象: [蚂蚁 ..., 小蚂蚁 ...] 拷贝之后的原型: [蚂蚁 ..., 小蚂蚁 ...] |
发现拷贝之后原来的对象持有的ArrayList<String>类型的nameList引用会随着拷贝得到的fromClone对象执行了setName()方法而改变,这不是我们想要的结果,因为这意味着原型以及拷贝得到的对象共享同一个引用变量,这是线程不安全的。当我们去掉上面clone()方法中被注释的语句之后再测试,得到结果如下:
拷贝之前的原型: [蚂蚁 ...] 拷贝得到的对象: [蚂蚁 ..., 小蚂蚁 ...] 拷贝之后的原型: [蚂蚁 ...] |
结果正确。其实,在Java中使用原型模式Prototype是相当简单的,只要记住几点注意点,就可以方便地实现该模式了。由于使用clone()方法来拷贝一个对象是从内存二进制流中进行IO读写,所以拷贝得到一个对象是不会执行该对象所对应类的构造函数的。总结如下:
1、构造函数不会被执行;
2、类的成员变量中若有引用类型的变量(数组也是一种对象),默认的clone()并不会对其进行拷贝,需自行提供深拷贝;
String类型与int、long、char等基本类型类似,默认地会被拷贝。
总之,Java中原型模式clone()方法对我们隐藏了许多细节,或者说必要操作,它的实现机制涉及到了反射、IO流操作、序列化等,只有弄清楚这一系列的知识才能更深入地理解这些相关的知识点。