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

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方法
		@Override
		public 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("构造函数被执行了...");
		}

		@Override
		public 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>();

		@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;
		}

		// 设置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>();

		@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;
		}
	}


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();
		}

		@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;
		}
	}

	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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值