设计模式之原型模式

我的理解:
原型模式怎么理解呢,原型,就是一个东西的复制品,为啥这样说,原型,就是原来的形状,
来张图就比较明确了
在这里插入图片描述生活中比较明显的例子就是,给用户发电子邮件,如果用户比较多的时候,300百万,我不可能单线程的发,那还不得等到和猴年马月,但是我们使用多线程的时候,存在这一个问题,一个线程,怎么知道另外一个线程给那些用户发了,那些用户没发,这个是非常重要的,多线程的世界里他们总是一起执行,但是彼此不知道对方执行的情况。
为了解决这个问题,我们就使用原型模式,如果发邮件,是一个 人,那不就行了,那些没发,那些发了自己不就非常清楚,但是人就一个,总不可能一分为多,也没有半个人吧,但是有一个比较好的法子,就是把它复制一份(“克隆人”),和原来的对象一毛一样,然后去执行它的工作,这样数据就不会乱,达到了统一的目的
广告类:

public class AdvTemplate {
//广告信名称
private String advSubject ="XX银行国庆信用卡抽奖活动";
//广告信内容
private String advContext = "国庆抽奖活动通知:只要刷卡就送你一百万!...";
//取得广告信的名称
public String getAdvSubject(){
return this.advSubject;
}
//取得广告信的内容
public String getAdvContext(){
return this.advContext;
}
}

		邮箱类
public class Mail implements Cloneable{
//收件人
private String receiver;
//邮件名称
private String subject;
//称谓
private String appellation;
//邮件内容
private String contxt;
//邮件的尾部,一般都是加上"XXX版权所有"等信息
private String tail;
//构造函数
public Mail(AdvTemplate advTemplate){
this.contxt = advTemplate.getAdvContext();
this.subject = advTemplate.getAdvSubject();
}
@Override
public Mail clone(){
Mail mail =null;
try {
mail = (Mail)super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return mail;
}
//以下为getter/setter方法
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getAppellation() {
return appellation;
}
public void setAppellation(String appellation) {
this.appellation = appellation;
}
public String getContxt() {
return contxt;
}
public void setContxt(String contxt) {
this.contxt = contxt;
}
public String getTail() {
return tail;
}
public void setTail(String tail) {
this.tail = tail;
}
}

原型模式的定义:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
原型模式的通用类图
在这里插入图片描述
原型模式的核心就是一个clone方法,通过改方法进行对象的拷贝
Java中怎么实现这个过程呢,,Java提供了一个Cloneable接口来标示这个对象是可拷贝的,翻开JDK的帮
助看看Cloneable是一个方法都没有的,这个接口只是一个标记作用,在JVM中具有这个标记的对象才有可能被拷贝。
那怎么才能从“有可能被拷贝”转换为“可以被拷贝”呢?方法是覆盖clone()方法,是的,你没有看错是重写clone()方法,下面类中的clone方法,如
代码:

public class PrototypeClass implements Cloneable{
//覆写父类Object方法
@Override
public PrototypeClass clone(){
PrototypeClass prototypeClass = null;
try {
prototypeClass = (PrototypeClass)super.clone();
} catch (CloneNotSupportedException e) {
//异常处理
}
return prototypeClass;
}
}

我们为啥要覆盖呢,Object 是所有对象的父类,它有方法clone();但是什么都没干,我重写父类方法就可以达到复制对象的效果
原型模式的特点

● 性能优良
原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一
个循环体内产生大量的对象时,原型模式可以更好地体现其优点
● 逃避构造函数的约束
这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,优点就是减少了约束,缺点也是减少了约束,使用时要注意
使用的地方
● 性能和安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
● 一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑
使用原型模式拷贝多个对象供调用者使用。
原型模式注意的地方
构造函数不会被执行
一个实现了Cloneable并重写了clone方法的类A,有一个无参构造或有参构造B,通过
new关键字产生了一个对象S,再然后通过S.clone()方式产生了一个新的对象T,那么在对象
拷贝时构造函数B是不会被执行的。
可以拷贝的Thing类

public class Thing implements Cloneable{
public Thing(){
System.out.println("构造函数被执行了...");
}
@Override
public Thing clone(){
Thing thing=null;
try {
thing = (Thing)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return thing;
}
}

然后我们再来写一个Client类,进行对象的拷贝

public class Client {
public static void main(String[] args) {
//产生一个对象
Thing thing = new Thing();
//拷贝一个对象
Thing cloneThing = thing.clone();
}
}

结果
在这里插入图片描述
对象拷贝时构造函数确实没有被执行,这点从原理来讲也是可以讲得通的,Object类的
clone方法的原理是从内存中(具体地说就是堆内存)以二进制流的方式进行拷贝,重新分配
一个内存块,那构造函数没有被执行也是非常正常的了。内存的分配和复制
浅拷贝和深拷贝
我们来回顾一些对象引用问题,以及对象复制问题
请添加图片描述

基本 数据类型的复制,是在栈里面,所以复制的时候,会拷贝一份,而引用类型的复制使用的时候只是,将引用地址给复制了一份,让新的引用指向那一片空间,本质还是同一个内存地址,
所以浅拷贝就是将应用地址拷贝了一份然后给了复制对象的引用。
那么我们怎么做的深度拷贝呢,很简单就是把要拷贝的对象里面的成员变量的地址也给拷贝一份,给新的对象成员变量就行。
代码清单13-10 浅拷贝

public class Thing implements Cloneable{![请添加图片描述](https://img-blog.csdnimg.cn/7289e0cb26bd4aa0a25d423f9ee46fd4.jpeg)

//定义一个私有变量
private ArrayList<String> arrayList = new ArrayList<String>();
@Override
public Thing clone(){
Thing thing=null;
try {
thing = (Thing)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return thing;
}
//设置HashMap的值
public void setValue(String value){
this.arrayList.add(value);
}
//取得arrayList的值
public ArrayList<String> getValue(){
return this.arrayList;
}
}

在Thing类中增加一个私有变量arrayLis,类型为ArrayList,然后通过setValue和getValue
分别进行设置和取值
场景类

public class Client {
public static void main(String[] args) {
//产生一个对象
Thing thing = new Thing();
//设置一个值
thing.setValue("张三");
//拷贝一个对象
Thing cloneThing = thing.clone();
cloneThing.setValue("李四");
System.out.println(thing.getValue());
}
}

猜想一下运行结果应该是什么?是仅一个“张三”吗?运行结果如下所示:
在这里插入图片描述
怎么会这样呢?怎么会有李四呢?是因为Java做了一个偷懒的拷贝动作,Object类提供
的方法clone只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象
的内部元素地址,这种拷贝就叫做浅拷贝。确实是非常浅,两个对象共享了一个私有变量,
你改我改大家都能改,是一种非常不安全的方式。

为什么在Mail那个类中就可以使用String类型,而不会产生由浅拷贝带来的问题呢?内部的数组和引用对象才不拷贝,
其他的原始类型比如int、long、char等都会被拷贝,但是对于String类型,Java就希望你把它
认为是基本类型,它是没有clone方法的,处理机制也比较特殊,通过字符串池(stringpool)
在需要的时候才在内存中创建新的字符串,读者在使用的时候就把String当做基本类使用即
可。
注意:
使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:

一是类的成员变量,而不是方法内变量;
二是必须是一个可变的引用对象,而不是一个原始类型或不可变对象。

深拷贝

public class Thing implements Cloneable{
//定义一个私有变量
private ArrayList<String> arrayList = new ArrayList<String>();
@Override
public Thing clone(){
Thing thing=null;
try {
thing = (Thing)super.clone();
thing.arrayList = (ArrayList<String>)this.arrayList.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return thing;
}
}

仅仅增加了粗体部分,对私有的类变量进行独立的拷贝(要拷贝就拷贝全)
该方法就实现了完全的拷贝,两个对象之间没有任何的瓜葛了,你修改你的,我修改我
的,不相互影响,这种拷贝就叫做深拷贝。为啥会出现这样呢,
clone与final两个冤家

对象的clone与对象内的final关键字是有冲突的,为啥这样说,你都final还想被修改,虽然你是复制的,但是你的引用类型还是指向的同一片内存空间。

增加final关键字的拷贝

public class Thing implements Cloneable{
//定义一个私有变量
private final ArrayList<String> arrayList = new ArrayList<String>();
@Override
public Thing clone(){
Thing thing=null;
try {
thing = (Thing)super.clone();
this.arrayList = (ArrayList<String>)this.arrayList.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return thing;
}
}

粗体部分仅仅增加了一个final关键字,然后编译器就报斜体部分错误,正常呀,final类
型你还想重赋值呀!你要实现深拷贝的梦想在final关键字的威胁下破灭了。
要使用clone方法,类的成员变量上不要增加final关键字。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奋斗中的代码猿--刘同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值