设计模式之07原型模式(笔记)

来源:互联网 发布:java工程师认证 编辑:程序博客网 时间:2024/06/05 05:12

1 定义:

1.1 定义:Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.(用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象)

1.2 通用类图:

原型模式的核心是一个clone方法,通过该方法进行对象的拷贝,Java提供了一个Cloneable接口来标示这个对象是可拷贝的。“标示”:在JDK的帮助中,Cloneable是一个方法都没有的,这个接口只是一个标记作用,在JVM中具有这个标记的对象才有可能被拷贝。

从可能到可以,还需要覆盖从Object继承下来的clone( )方法。

1.3 通用代码:

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


2 优点

2.1 性能优良:原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。

2.2 逃避构造函数的约束:也是缺点,直接在内存中拷贝,构造函数是不会执行的

3 缺点

如上2.2.

4 应用场景

4.1 资源优化场景:类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等;

4.2 性能和安全要求的场景:通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以用原型模式。(即回避new的调用

4.3 一个对象多个修改者的场景:一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。(这个有点不敢苟同:改写的结果如何汇总?莫非是其他对象只修改此对象的某一特定值域或成员?

原型模式很少单独出现,一般是工厂方法模式(可以创建对象,供其复制嘛)一起出现,通过clone的方法创建另一个对象,然后由工厂方法提供给调用者。

5 注意事项

5.1 构造函数不会被执行:

bject类的clone方法的原理是从内存中(具体地说是堆内存)以二进制的方式进行拷贝,重新分配一个内存块,那构造函数没有执行也是非常正常的了。

源代码示例:

public class Thing implements Cloneable {public Thing() {System.out.println("构造函数被执行了...");}@Overridepublic Thing clone() {Thing thing = null;try {thing = (Thing) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return thing;}}public class Client {public static void main(String[] args) {// 产生一个对象Thing thing = new Thing();// 拷贝一个对象Thing cloneThing = thing.clone();}}

构造函数在clone()时,没有被调用。

5.2 浅拷贝和深拷贝:

ava做了一个偷懒的拷贝动作:bject类提供的方法clone只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝

那么tring类型 呢?

内部的数组和引用对象才不拷贝,其他的原始类型比如intlongchar等都会拷贝,但是对于String类型,Java就希望你把它认为是基本类型,它是没有clone方法的,处理机制也比较特殊,通过字符串池(stringpool)在需要时才在内在中创建新的字符串,读者在使用的时候把String当基本类使用即可。

注意一:使用原型模式时,引用的成员变量必须满足两个条件才会被拷贝:一是类的成员变量,而不是方法内的变量;二是必须是一个可变的引用对象,而不是一个原始类型或不可变对象。

注意二:深拷贝与浅拷贝建议不要混合用,特别是在涉及类的继承时,父类有多个引用的情况就非常复杂,建议的方案是深拷贝和浅拷贝分开实现。

如何进行深拷贝呢?对类变量(待深拷贝类的成员)进行独立的拷贝。

源代码:拷贝与拷贝

public class Thing implements Cloneable {// 定义一个私有变量private final ArrayList<String> arrayList = new ArrayList<String>();@Overridepublic Thing clone() {Thing thing = null;try {thing = (Thing) super.clone(); // 仅此一句为 浅拷贝// this.arrayList =// (ArrayList<String>)this.arrayList.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;}}// 测试:public class Client {public static void main(String[] args) {// 产生一个对象Thing thing = new Thing();// 设置一个值thing.setValue("张三");thing.setValue("李四");// 拷贝一个对象Thing cloneThing = thing.clone();cloneThing.setValue("李四");System.out.println(thing.getValue());}}

浅拷贝会有“张三 李四”, 深拷贝只有“张三”。

5.3 clonefinal关键字的冲突:(要使用clone()方法,类的成员变量上就不要增加final关键字)。

类内部成员若定义为final型,则意味着,定义时便一次性的赋值了。那么通过clone( )方法返回的值又给谁呢?

如下代码,会在标注处:编译器报斜体部分错误。

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


6 扩展

暂无

7 范例

(批量发送邮件实例)

类图如下:


源代码如下:

package _07_Prototype;/** * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you *         all. 需要发送出去的邮件账单 */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();}@Overridepublic Mail clone() {Mail mail = null;try {mail = (Mail) super.clone();} catch (CloneNotSupportedException e) {// TODO Auto-generated catch blocke.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;}}public class AdvTemplate {// 广告信名称private String advSubject = "XX银行国庆信用卡抽奖活动";// 广告信内容private String advContext = "国庆抽奖活动通知:只要刷卡就送你1百万!....";// 取得广告信的名称public String getAdvSubject() {return this.advSubject;}// 取得广告信的内容public String getAdvContext() {return this.advContext;}}public class Client {// 发送账单的数量,这个值是从数据库中获得private static int MAX_COUNT = 10000000;// 每次查看一种方式。以进行比较。public static void main(String[] args) {long begin = System.currentTimeMillis();usePrototype();long end = System.currentTimeMillis();System.out.println("使用原型模式发送1000000封邮件历时:" + (end - begin) / 1000+ "s");// long begin = System.currentTimeMillis();// useNew();// long end = System.currentTimeMillis();// System.out.println("使用new发送1000000封邮件历时:" + (end - begin) / 1000// + "s");}public static void usePrototype() {// 模拟发送邮件int i = 0;// 把模板定义出来,这个是从数据中获得Mail mail = new Mail(new AdvTemplate());mail.setTail("XX银行版权所有");while (i < MAX_COUNT) {// 以下是每封邮件不同的地方Mail cloneMail = mail.clone();cloneMail.setAppellation(getRandString(5) + " 先生(女士)");cloneMail.setReceiver(getRandString(5) + "@" + getRandString(8)+ ".com");// 然后发送邮件sendMail(cloneMail);i++;}}public static void useNew() {// 模拟发送邮件int i = 0;while (i < MAX_COUNT) {// 把模板定义出来,这个是从数据中获得Mail mail = new Mail(new AdvTemplate());mail.setTail("XX银行版权所有");mail.setAppellation(getRandString(5) + " 先生(女士)");mail.setReceiver(getRandString(5) + "@" + getRandString(8)+ ".com");// 然后发送邮件sendMail(mail);i++;}}// 发送邮件public static void sendMail(Mail mail) {// System.out.println("标题:"+mail.getSubject() +// "\t收件人:"+mail.getReceiver()+"\t....发送成功!");}// 获得指定长度的随机字符串public static String getRandString(int maxLength) {String source = "abcdefghijklmnopqrskuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";StringBuffer sb = new StringBuffer();Random rand = new Random();for (int i = 0; i < maxLength; i++) {sb.append(source.charAt(rand.nextInt(source.length())));}return sb.toString();}}


实测结果:

使用原型模式发送1000000封邮件历时:31s

使用new发送1000000封邮件历时:29s

可能的原因:所需要复制的单项数据太小,体现不出差距