原型模式,是一种简单易学的设计模式。
当一个对象非常复杂,非常大的时候,创建起来成本较大,这时候就可以使用原型模式来完成这个复杂对象的创建。
原型模式到底干了什么呢?
简单来说就是基于一个对象复制它(也可以叫克隆),生成一个新对象,并且和被复制的对象一模一样,只是内存地址不同,这就是原型模式。
不知道大家有没有看过电影《黑客帝国》,电影男主尼奥,在虚拟世界中和电脑创造的机器人对抗,其中的一个片段是机器人会分身,包括男主尼奥也能分身,这种分身技能,就和原型模式很贴切,可以基于一个原型,快速复制一个一模一样的。
如果你没有看过《黑客帝国》也没关系,我们的四大名著之一《西游记》中的孙悟空,拔一根猴毛就变出无数各自己,也是这个原理,O(∩_∩)O。
我们接下来就以电影中的尼奥变分身为例,来具体讲讲原型模式。
1、新建Neo类,表示是尼奥本人
package com.itzhimei.study.design.clone;
import java.util.List;
/**
* www.itzhimei.com
* Neo是尼奥的英文名
*/
public class Neo implements Cloneable {
private List<Integer> bullets;
public Neo(List<Integer> bullets) {
this.bullets = bullets;
}
/**
* 战斗技能--开枪
* bullets 子弹
*/
public void fire() {
//三连发点射,消耗散发子弹
for(int i=0; i<3; i++) {
bullets.remove(0);
}
}
/**
* 检查剩余弹药
* bullets 子弹
*/
public void check() {
//检查剩余弹药
System.out.println("剩余子弹数"+ bullets.size());
bullets.forEach(System.out::println);
}
@Override
protected Neo clone() {
Neo n = null;
try {
//super.clone()其实是Object的clone方法,所有类的父类都是Object
n=(Neo)super.clone();
} catch(Exception e) {
//CloneNotSupportedException
e.printStackTrace();
}
return n;
}
}
这里重点要看public class Neo implements Cloneable,类实现了Cloneable接口,在Java中,如果一个类要实现原型模式的功能,需要实现这个接口,同时覆写clone()方法。
我们在尼奥类中,定义了“战斗开枪”方法–fire(),“检查弹药”方法–check(),还有一个召唤分身方法–clone(),这是尼奥具备的技能。
2、测试
package com.itzhimei.study.design.clone;
import java.util.ArrayList;
import java.util.List;
/**
* www.itzhimei.com
* 测试
*/
public class client {
public static void main(String[] args) {
List<Integer> bullets = new ArrayList<>();
bullets.add(1);
bullets.add(2);
bullets.add(3);
bullets.add(4);
bullets.add(5);
bullets.add(6);
Neo n1 = new Neo(bullets);
Neo n2 = n1.clone();
n1.fire();
n1.check();
n2.fire();
n2.check();
}
}
我们在测试类中定义了一个尼奥的本体,然后又复制了一个尼奥的分身,然后用真身和分身分别战斗开枪和检查弹药,有趣的事情发生了,看输出结果:
//尼奥n1的输出
剩余子弹数3
4
5
6
//尼奥n2的输出
剩余子弹数0
我们从结果上来看,n1也就是尼奥的真身,开枪发射了散发子弹,检查弹药剩余三发子弹,并且打印了出来,是4、5、6
然后分身也开了三枪,检查弹药后发现没子弹了。
通过这个结果你得出结论了吗?
对的,真身和分身中的子弹List是一个,也即是虽然分身了,但是枪里的子弹却是共享的,真身尼奥开一枪,分身尼奥的枪里子弹也少了,这是什么原理?
这就是原型模式中的一个重要知识点:浅复制和深复制。
我们上面的例子演示的就是浅复制。
什么是浅复制呢?
Java中浅复制就是在执行Object的clone(),默认拷贝值针对的是基础类型,如int、float,String也会复制值,对于数组、集合和引用类型不会复制其里面的值,而只是拷贝走它们的内存地址给分身。所以上面就会产生不管修改真身还是分身,他们的子弹都会减少。
怎样做到深复制呢?
就是在上面的clone()方法中,对数组再进行依次clone,并把新clone的数组赋值给新克隆的对象。如下代码:
@Override
protected DeepCopyNeo clone() {
DeepCopyNeo n = null;
try {
n=(DeepCopyNeo)super.clone();
n.bullets = (ArrayList<Integer>) this.bullets.clone();
} catch(Exception e) {
//CloneNotSupportedException
e.printStackTrace();
}
return n;
}
重点看这两行:
n=(DeepCopyNeo)super.clone();
n.bullets = (ArrayList<Integer>) this.bullets.clone();
第二行就完成了数组的克隆。
我们贴上完整代码,并查看运行结果:
1、新建支持深复制的尼奥
package com.itzhimei.study.design.clone;
import java.util.ArrayList;
import java.util.List;
/**
* www.itzhimei.com
* Neo是尼奥的英文名
*/
public class DeepCopyNeo implements Cloneable {
private ArrayList<Integer> bullets;
public DeepCopyNeo(ArrayList<Integer> bullets) {
this.bullets = bullets;
}
/**
* 战斗技能--开枪
* bullets 子弹
*/
public void fire() {
//三连发点射,消耗散发子弹
for(int i=0; i<3; i++) {
bullets.remove(0);
}
}
/**
* 检查剩余弹药
* bullets 子弹
*/
public void check() {
//检查剩余弹药
System.out.println("剩余子弹数"+ bullets.size());
bullets.forEach(System.out::println);
}
@Override
protected DeepCopyNeo clone() {
DeepCopyNeo n = null;
try {
n=(DeepCopyNeo)super.clone();
n.bullets = (ArrayList<Integer>) this.bullets.clone();
} catch(Exception e) {
//CloneNotSupportedException
e.printStackTrace();
}
return n;
}
}
上面代码中,clone方法多了一行数组的clone
n.bullets = (ArrayList<Integer>) this.bullets.clone();
2、新建测试类
package com.itzhimei.study.design.clone;
import java.util.ArrayList;
import java.util.List;
/**
* www.itzhimei.com
* 测试
*/
public class DeepCopyClient {
public static void main(String[] args) {
ArrayList<Integer> bullets = new ArrayList<>();
bullets.add(1);
bullets.add(2);
bullets.add(3);
bullets.add(4);
bullets.add(5);
bullets.add(6);
DeepCopyNeo n1 = new DeepCopyNeo(bullets);
DeepCopyNeo n2 = n1.clone();
n1.fire();
n1.check();
n2.fire();
n2.check();
}
}
输出结果:
剩余子弹数3
4
5
6
剩余子弹数3
4
5
6
从结果能看出,两次调用开火方法fire(),都开了三枪,并且使用的都是前三发子弹,剩余的子弹都是4、5、6.
总结:
原型模式就是帮助我们快速赋值一个对象,当一个对象非常复杂,非常大,创建起来成本较大,这时候就可以使用原型模式来完成站着复杂对象的创建。
原型模式分为浅复制和深复制。
浅复制拷贝的是原始类型的值,也就是基础类型和字符串,遇到引用类型,赋值的是引用类型的内存地址,复制前后两个对象中的引用的对象是同一个,也就是指向内存的同一个区域。
深复制我们自己在clone方法中将应用对象也clone一份,达到克隆后的对象与被克隆对象中的元素完全独立的效果。