菜鸟之路-浅谈设计模式之原型模式

原型模式

定义:

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
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


如果是以前,我们都会直接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


我们希望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


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对象之间的关系是不是很像第一张呢?
可见这其实就是一个嵌套的克隆,嘿嘿

好吧,小伙伴,你明白了么?

这篇写的好累,不过值得,毕竟你都看到这了,谢谢大笑

参考文章链接:


  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值