一、原型模式的定义
原型模式是一个非常简单的设计模式,也是一种创建型设计模式。原型模式允许一个对象在创建另一个可定制的对象。根本无需知道任何如何创建的细节。就像孙悟空拔了一根毫毛一吹就招来了一群孙悟空一样。我们都不知道是怎么创建的。
在原型模式中,我们需要定义一个克隆的接口或者包含克隆方法的类,让具体的原型类去实现它,或者重写它。当我们需要创建对象时,我们就可以在client客户端调用具体原型类的克隆方法实现对象的复制创建。以下是原型模式的UML图:
二、原型模式的使用
2.1 前提知识
在Java语言下、Java提供了一个Cloneable 接口来标识这个对象是允许拷贝的。为什么说是“标识”呢,因为这个接口什么方法也没有,只是提供了一个标示的作用。
public interface Cloneable {
}
那么怎么才能从允许拷贝转换到可以真正的能拷贝呢?方法是我们重写clone()方法。你沒有看错,在Java中每个类的祖宗都是Object类。在Object类中是默认有clone方法的:
protected native Object clone() throws CloneNotSupportedException;
是不是觉得非常的简单,下面我们通过美猴王的例子实现一下。
2.2快速实现
我们首先定义一个美猴王类MonkeyKing,该类有两个属性:名称和年龄,并实现 Cloneable接口,简单的重写clone方法就可以了:
//美猴王
public class MonkeyKing implements Cloneable{
//名称
private String name;
//年龄
private int age;
//构造方法
public MonkeyKing() {
}
//重写Object类的clone
@Override
protected MonkeyKing clone() {
MonkeyKing monkeyKing= null;
try {
monkeyKing = (MonkeyKing) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return monkeyKing;
}
客户端(这里在main方法中)使用原型模式创建:
public static void main(String[] args) {
//孙悟空本体
MonkeyKing monkeyKing = new MonkeyKing();
monkeyKing.setName("孙悟空");
monkeyKing.setAge(3000);
int i = 0;
// 创建猴子的个数
int COUNT = 100;
while (i < COUNT){
MonkeyKing cmonkeyKing = monkeyKing.clone();
i++;
System.out.println(cmonkeyKing.toString());
}
}
结果:
MonkeyKing{name=‘孙悟空’, age=3000}
MonkeyKing{name=‘孙悟空’, age=3000}
MonkeyKing{name=‘孙悟空’, age=3000}
MonkeyKing{name=‘孙悟空’, age=3000}
…
通过以上的简单的操作,我们就实现了对象的复制。
三、深拷贝和浅拷贝:原型模式下的金箍棒
我们知道孙悟空是有一个神兵,就是定海神针,不是,是如意金箍棒。孙悟空本身只有一个金箍棒,如果给孙悟空这个对象添加一个兵器的对象。那么,再使用以上的方式进行克隆时候,金箍棒是不是也被同时克隆了呢?我们先代码看看:
我们新建Arms兵器类,属性有名称name和重量weight。
//兵器
public class Arms {
private int weight;
private String name;
。。。。
}
我们让美猴王对象有这么一个兵器:
//每猴王
public class MonkeyKing implements Cloneable {
//名称
private String name;
//年龄
private int age;
//兵器
private Arms arms;
public MonkeyKing() {
}
修改之前的客户端,使用同样的方式克隆美猴王对象,并通过对比美猴王对象的hashCode、兵器的hashcode,判断每一个克隆的美猴王对象与本体是否一样,以及兵器是否一样:
public static void main(String[] args) {
//孙悟空本体
MonkeyKing monkeyKing = new MonkeyKing();
monkeyKing.setName("孙悟空");
monkeyKing.setAge(3000);
Arms arms = new Arms();
arms.setName("金箍棒");
arms.setWeight(180000);
monkeyKing.setArms(arms);
int i = 0;
// 创建猴子的个数
int COUNT = 100;
while (i < COUNT) {
MonkeyKing cmonkeyKing = monkeyKing.clone();
i++;
System.out.println(cmonkeyKing.toString());
int monkeyKinghashCode = cmonkeyKing.hashCode();
if (monkeyKinghashCode == monkeyKing.hashCode()) {
System.out.println("克隆的美猴王是一个对象");
} else {
System.out.println("克隆的美猴王不是一个对象");
}
int hashCode = cmonkeyKing.getArms().hashCode();
if (hashCode == arms.hashCode()) {
System.out.println("克隆的兵器是同一个对象");
} else {
System.out.println("克隆的兵器不是同一个对象");
}
}
}
}
结果:
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器是同一个对象
我们可以看到美猴王对象都是新的一个对象,而兵器却都是一个。所有的美猴王持有的兵器的引用都是指向一个兵器对象。由此我们可以下一个结论:通过简单的clone方法,对于数据类型是基本数据类型的成员变量,是直接的复制,而对于引用数据类型,比如成员变量是某一个数组、对象,那么只会进行引用传递,只是将该成员的引用复制给了对象而已。我们称之为:浅拷贝。 当然有浅拷贝就有深拷贝。如何能够实现成员变量是引用数据类型是也能复制实现深拷贝呢?
四、深拷贝的实现方法
4.1 重写克隆方法实现深拷贝
我们首先将兵器类实现Cloneable接口,添加克隆方法
public class Arms implements Cloneable {
private int weight;
private String name;
@Override
protected Arms clone() {
Arms arms = null;
try {
arms = (Arms) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return arms;
}
....
再重写美猴王的clone方法
package com.cog.singleton.prototype;
//每猴王
public class MonkeyKing implements Cloneable {
//名称
private String name;
//年龄
private int age;
//兵器
private Arms arms;
public MonkeyKing() {
}
@Override
protected MonkeyKing clone() {
MonkeyKing monkeyKing = null;
try {
//实现基本类型的克隆
monkeyKing = (MonkeyKing) super.clone();
//引用对象单独的克隆
monkeyKing.arms = (Arms) monkeyKing.arms.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return monkeyKing;
}
这样我们通过客户端再次创建100个美猴王:
public static void main(String[] args) {
//孙悟空本体
MonkeyKing monkeyKing = new MonkeyKing();
monkeyKing.setName("孙悟空");
monkeyKing.setAge(3000);
Arms arms = new Arms();
arms.setName("金箍棒");
arms.setWeight(180000);
monkeyKing.setArms(arms);
int i = 0;
// 创建猴子的个数
int COUNT = 100;
while (i < COUNT) {
MonkeyKing cmonkeyKing = monkeyKing.clone();
i++;
System.out.println(cmonkeyKing.toString());
int monkeyKinghashCode = cmonkeyKing.hashCode();
if (monkeyKinghashCode == monkeyKing.hashCode()) {
System.out.println("克隆的美猴王是一个对象");
} else {
System.out.println("克隆的美猴王不是一个对象");
}
int hashCode = cmonkeyKing.getArms().hashCode();
if (hashCode == arms.hashCode()) {
System.out.println("克隆的兵器是同一个对象");
} else {
System.out.println("克隆的兵器不是同一个对象");
}
}
}
结果:
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象
我们发现兵器也实现了克隆。
4.2 通过序列化的方式实现深拷贝
首先,让美猴王类和兵器类都是实现Serializable接口。接着我们在美猴王类中自己写一个deepClone的方法:
//每猴王
public class MonkeyKing implements Cloneable, Serializable {
//名称
private String name;
//年龄
private int age;
//兵器
private Arms arms;
public MonkeyKing() {
}
@Override
protected MonkeyKing clone() {
MonkeyKing monkeyKing = null;
try {
//实现基本类型的克隆
monkeyKing = (MonkeyKing) super.clone();
//引用对象单独的克隆
monkeyKing.arms = (Arms) monkeyKing.arms.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return monkeyKing;
}
//使用序列化的方法
public MonkeyKing deepClone() {
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
MonkeyKing monkeyKing = (MonkeyKing) ois.readObject();
return monkeyKing;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e) {
}
}
}
客户端克隆:
public static void main(String[] args) {
//孙悟空本体
MonkeyKing monkeyKing = new MonkeyKing();
monkeyKing.setName("孙悟空");
monkeyKing.setAge(3000);
Arms arms = new Arms();
arms.setName("金箍棒");
arms.setWeight(180000);
monkeyKing.setArms(arms);
int i = 0;
// 创建猴子的个数
int COUNT = 100;
while (i < COUNT) {
MonkeyKing cmonkeyKing = monkeyKing.deepClone();
i++;
System.out.println(cmonkeyKing.toString());
int monkeyKinghashCode = cmonkeyKing.hashCode();
if (monkeyKinghashCode == monkeyKing.hashCode()) {
System.out.println("克隆的美猴王是一个对象");
} else {
System.out.println("克隆的美猴王不是一个对象");
}
int hashCode = cmonkeyKing.getArms().hashCode();
if (hashCode == arms.hashCode()) {
System.out.println("克隆的兵器是同一个对象");
} else {
System.out.println("克隆的兵器不是同一个对象");
}
}
}
通过以上的写法也可以实现:
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象
MonkeyKing{name=‘孙悟空’, age=3000, arms=Arms{weight=180000, name=‘金箍棒’}}
克隆的美猴王不是一个对象
克隆的兵器不是同一个对象
五、小结
原型模式是先生产出一个包含大量共有信息的类,然后通过拷贝生成一个个这样对象。看过黑客帝国的人都知道。反派人物就是一个人通过复制,复制出大量的反派,最后主角也被复制了。今天讲的原型模式就是这样的概念。