原型模式
定义:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
Prototype原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
Prototype原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
解决什么问题:
它主要面对的问题是:“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有比较稳定一致的接口。Java中的克隆方法
Java的所有类都是从java.lang.Object类继承而来的,而Object类提供protected Object clone()方法对对象进行复制,子类当然也可以把这个方法置换掉,提供满足自己需要的复制方法。对象的复制有一个基本问题,就是对象通常都有对其他的对象的引用。当使用Object类的clone()方法来复制一个对象时,此对象对其他对象的引用也同时会被复制一份Java语言提供的Cloneable接口只起一个作用,就是在运行时期通知Java虚拟机可以安全地在这个类上使用clone()方法。通过调用这个clone()方法可以得到一个对象的复制。由于Object类本身并不实现Cloneable接口,因此如果所考虑的类没有实现Cloneable接口时,调用clone()方法会抛出CloneNotSupportedException异常。
克隆满足的条件
clone()方法将对象复制了一份并返还给调用者。所谓“复制”的含义与clone()方法是怎么实现的。一般而言,clone()方法满足以下的描述:(1)对任何的对象x,都有:x.clone()!=x。换言之,克隆对象与原对象不是同一个对象。
(2)对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原对象的类型一样。
(3)如果对象x的equals()方法定义其恰当的话,那么x.clone().equals(x)应当成立的。
有朋友说,说的好抽象啊。。。是啊,真的好抽象啊。。。好吧,那我们来个活生生的栗子看看吧
首先,创建一个简历类实现Cloneable接口
public class OldJianLi implements Cloneable{
private String name;
private int age;
public OldJianLi(String name,int age) {
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void Display(){
System.out.println("我叫:"+name+",今年"+age+"岁");
}
public Object clone()
{
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("*** is not Cloneable");
}
return object;
}
}
创建客户端类
public class OldClient {
public static void main(String[] args) {
OldJianLi a=new OldJianLi("张三", 20);
OldJianLi b=(OldJianLi) a.clone();
OldJianLi c=(OldJianLi) a.clone();
a.Display();
b.Display();
c.Display();
System.out.println(a+"..."+a.getName().hashCode());
System.out.println(b+"..."+b.getName().hashCode());
System.out.println(c+"..."+c.getName().hashCode());
}
}
输出结果:
我叫:张三,今年20岁
我叫:张三,今年20岁
我叫:张三,今年20岁
bbb.OldJianLi@47ca3f82...774889
bbb.OldJianLi@2f0f94a0...774889
bbb.OldJianLi@27e6ac83...774889
我叫:张三,今年20岁
我叫:张三,今年20岁
bbb.OldJianLi@47ca3f82...774889
bbb.OldJianLi@2f0f94a0...774889
bbb.OldJianLi@27e6ac83...774889
如果是以前,我们都会直接new 3个对象,但是每new 一次都执行一次构造函数,如果构造函数的执行时间很长,那么多次执行会降低效率,而克隆可以有效的解决这个问题。我们再看看,a,b,c对象的地址不同,他们属性值是相同的(可见克隆还是会创建新对象,不过省去的构造函数的执行)
那么假设我们要给简历加个工作经验的类呢?如下
给简历加个工作经验的类
//工作经历类
public class Work{
//公司名字
private String workName;
//工作年限
private int workAge;
public String getWorkName() {
return workName;
}
public void setWorkName(String workName) {
this.workName = workName;
}
public int getWorkAge() {
return workAge;
}
public void setWorkAge(int workAge) {
this.workAge = workAge;
}
}
public class JianLi implements Cloneable{
private String name;
private int age;
private Work work;
public JianLi(String name,int age) {
this.name=name;
this.age=age;
work=new Work();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Work getWork() {
return work;
}
public void setWork(Work work) {
this.work = work;
}
public void Display(){
System.out.println("我叫:"+name+",今年"+age+"岁");
System.out.println("我公司名字"+work.getWorkName());
}
public Object clone()
{
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("*** is not Cloneable");
}
return object;
}
}
public class Client {
public static void main(String[] args) {
JianLi a=new JianLi("张三", 20);
a.getWork().setWorkName("aaa");
JianLi b=(JianLi) a.clone();
b.getWork().setWorkName("bbb");
JianLi c=(JianLi) a.clone();
c.getWork().setWorkName("ccc");
a.Display();
b.Display();
c.Display();
System.out.println("a地址:"+a);
System.out.println("b地址:"+b);
System.out.println("c地址:"+c);
System.out.println("a的Work地址:"+a.getWork()+".a公司名"+a.getWork().getWorkName());
System.out.println("b的Work地址:"+b.getWork()+".b公司名"+b.getWork().getWorkName());
System.out.println("c的Work地址:"+c.getWork()+".c公司名"+c.getWork().getWorkName());
}
}
输出结果:
我叫:张三,今年20岁
我公司名字ccc
我叫:张三,今年20岁
我公司名字ccc
我叫:张三,今年20岁
我公司名字ccc
a地址:bbb.JianLi@672d34a6
b地址:bbb.JianLi@48dbb335
c地址:bbb.JianLi@4fd281f1
a的Work地址:bbb.Work@5511e28.a公司名ccc
b的Work地址:bbb.Work@5511e28.b公司名ccc
c的Work地址:bbb.Work@5511e28.c公司名ccc
我公司名字ccc
我叫:张三,今年20岁
我公司名字ccc
我叫:张三,今年20岁
我公司名字ccc
a地址:bbb.JianLi@672d34a6
b地址:bbb.JianLi@48dbb335
c地址:bbb.JianLi@4fd281f1
a的Work地址:bbb.Work@5511e28.a公司名ccc
b的Work地址:bbb.Work@5511e28.b公司名ccc
c的Work地址:bbb.Work@5511e28.c公司名ccc
我们希望3份简历的公司名字不一样,所以给3个对象设置了不同的公司名,可最终还是输出一样的公司名。怎么回事?
看看以下的概念
浅克隆和深克隆
无论你是自己实现克隆方法,还是采用Java提供的克隆方法,都存在一个浅度克隆和深度克隆的问题。浅度克隆
只负责克隆按值传递的数据(比如基本数据类型、String类型),而不复制它所引用的对象,换言之,所有的对其他对象的引用都仍然指向原来的对象。
说的有点抽象,这几天刚好买了本《大话设计模式》,里面是这么解释的,如果字段是值类型的,则对该字段执行逐位复制,如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象与其复本引用同一对象。(所以上面输出的a,b,c的工作经验对象的地址是一样的)
深度克隆
除了浅度克隆要克隆的值外,还负责克隆引用类型的数据。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深度克隆把要复制的对象所引用的对象都复制了一遍,而这种对被引用到的对象的复制叫做间接复制。深度克隆要深入到多少层,是一个不易确定的问题。在决定以深度克隆的方式复制一个对象的时候,必须决定对间接复制的对象时采取浅度克隆还是继续采用深度克隆。因此,在采取深度克隆时,需要决定多深才算深。此外,在深度克隆的过程中,很可能会出现循环引用的问题,必须小心处理。
(为了实现上面我们希望的3份简历的公司名字不一样,修改下代码)
让工作经验类也实现Cloneable接口
//工作经历类
public class Work implements Cloneable{
//公司名字
private String workName;
//工作年限
private int workAge;
public String getWorkName() {
return workName;
}
public void setWorkName(String workName) {
this.workName = workName;
}
public int getWorkAge() {
return workAge;
}
public void setWorkAge(int workAge) {
this.workAge = workAge;
}
public Object clone()
{
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("*** is not Cloneable");
}
return object;
}
}
简历类添加了一个构造方法,并修改了克隆方法
public class JianLi implements Cloneable{
private String name;
private int age;
private Work work;
public JianLi(String name,int age) {
this.name=name;
this.age=age;
work=new Work();
}
public JianLi(Work work) {
this.work=(Work) work.clone();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Work getWork() {
return work;
}
public void setWork(Work work) {
this.work = work;
}
public void Display(){
System.out.println("我叫:"+name+",今年"+age+"岁");
System.out.println("我公司名字"+work.getWorkName());
}
public Object clone()
{
JianLi obj=new JianLi(this.work);
obj.age=this.age;
obj.name=this.name;
return obj;
}
}
输出结果:
我叫:张三,今年20岁
我公司名字aaa
我叫:张三,今年20岁
我公司名字bbb
我叫:张三,今年20岁
我公司名字ccc
a地址:bbb.JianLi@4fd281f1
b地址:bbb.JianLi@5511e28
c地址:bbb.JianLi@1092d6d2
a的Work地址:bbb.Work@18557d7e.a公司名aaa
b的Work地址:bbb.Work@14a1e7ad.b公司名bbb
c的Work地址:bbb.Work@11711970.c公司名ccc
我公司名字aaa
我叫:张三,今年20岁
我公司名字bbb
我叫:张三,今年20岁
我公司名字ccc
a地址:bbb.JianLi@4fd281f1
b地址:bbb.JianLi@5511e28
c地址:bbb.JianLi@1092d6d2
a的Work地址:bbb.Work@18557d7e.a公司名aaa
b的Work地址:bbb.Work@14a1e7ad.b公司名bbb
c的Work地址:bbb.Work@11711970.c公司名ccc
OK,我们实现了目标,细心的你一定发现了,这次a,b,c工作经验对象的地址不同了
为了你更好理解,我画了张丑图,别介
(图里的方框代表一个新对象)
浅克隆字段是值类型的,就好像第一张图,a把name,age都复制到b,c上了(但是b,c都是一个新对象了,外面我画了方框)
如果是引用类型的,就好像第二张图,b,c都直接引用a对象的work对象,而没有重新创建自己的work对象
深克隆如第三张图,b,c都创建了一个work对象,然后a把workName,workAge都复制到b,c上了,如果仔细观察,第三张图里的3个work对象之间的关系是不是很像第一张呢?
可见这其实就是一个嵌套的克隆,嘿嘿
好吧,小伙伴,你明白了么?
这篇写的好累,不过值得,毕竟你都看到这了,谢谢
参考文章链接: