设计模式之原型模式

创建型模式之原型模式

定义

  • 原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
  • 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要 在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
  • 原型模式可以分为浅克隆和深度克隆

角色

角色

java语言中实现克隆的两种方式

  1. 直接创建一个对象,然后设置成员变量的值
Obj obj=new Obj(); //创建一个新的对象
obj.setName(this.name);   //设置其中变量的值
obj.setAge(this.age);
  1. 实现cloneable接口

浅克隆

  • 如果克隆的对象的成员变量是值类型的,比如int,double那么使用浅克隆就可以实现克隆完整的原型对象,但是如果其中的成员变量有引用类型的,那么这个引用类型的克隆过去的其实是地址,克隆对象的这个引用类型变量改变了,那么原来变量的值也是会改变的。
  • 简单的说,浅克隆只能复制值类型的,对于引用类型的数据只能复制地址

浅克隆

实例

  • 一个公司出版周报,那么这个周报的格式一般是相同的,只是将其中的内容稍作修改即可。但是一开始没有这个原型,员工每周都需要重新手写这个周报,现在有了这个周报的原型,只需要在这个clone这个原型,然后在其基础上修改即可。

周报

  • 其中的Cloneable就是抽象原型类
  • 附件类(这个是一个引用类型的对象,验证浅克隆只是复制其中的地址,如果两个对象中的任何一个改变了这个变量的值,那么另外一个也会随之改变)
/*
 * 附件类,这个是周报的附件
 */
public class Attachment {
	private String name; // 名称

	public Attachment(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}
  • 周报的类(其中实现了Cloneable接口)
    • 其中的clone()方法返回的就是一个克隆的对象,因此我们调用这个方法克隆一个新的对象
/*
 * 这个是周报类,这个类是实现接口Prototype这个接口的
 */
public class WeeklyLog implements Cloneable {
	private String name; // 姓名
	private String date; // 日期
	private String content; // 内容
	private Attachment attachment;  //附件,是一个引用对象,这个只能实现浅克隆
	public WeeklyLog() {
		super();
	}
	/**
	 * 构造方法
	 */
	public WeeklyLog(String name, String date, String content) {
		super();
		this.name = name;
		this.date = date;
		this.content = content;
	}
	/**
	 * 提供一个clone方法,返回的是一个clone对象
	 */
	public WeeklyLog clone() {
		Object object = null; // 创建一个Object对象
		try {
			object = super.clone(); // 直接调用clone方法,复制对象
			return (WeeklyLog) object; // 返回即可
		} catch (CloneNotSupportedException e) {
			System.out.println("这个对象不能复制.....");
			return null;
		}
	}

}
  • 测试类
    • 测试浅克隆的值类型是是否完成复制了
    • 测试引用类型的值能否完成克隆,还是只是复制了一个引用地址
    • 从结果来看,对象是完成复制了,因为判断两个对象的地址是不一样的,但是其中的引用类型的成员变量没有完成复制,只是复制了一个地址
public class Client {
	public static void main(String[] args) throws CloneNotSupportedException {

		WeeklyLog p1 = new WeeklyLog("陈加兵", "第一周", "获得劳动模范的称号..."); // 创建一个对象
		Attachment attachment = new Attachment("消息");
		p1.setAttachment(attachment); // 添加附件
		WeeklyLog p2 = p1.clone();
		System.out.println(p1 == p2); // 判断是否正确
		p2.setName("Jack"); // 修改P2对象的内容
		p2.setDate("第二周");
		p2.setContent("工作认真.....");
		System.out.println(p2.getName());
		// 返回true,可以知道这两个附件的地址是一样的
		System.out.println(p1.getAttachment() == p2.getAttachment());
	}
}

总结

  • 浅克隆对于值类型的数据可以复制成功,但是对于引用卡类型的数据只能复制一个地址,如果一个对象中的引用类型的变量的值改变了,那么另外一个也会随之改变

深度克隆

  • 浅克隆只能完成复制值类型,深度克隆可以完成复制引用类型和值类型

深度克隆

条件

  1. 引用类型的变量类实现序列化(实现Serializabl接口)
  2. 需要克隆的类实现序列化(实现Serializable接口)

为什么实现序列化

  • 因为深度克隆的实现的原理是使用输入和输出流,如果想要将一个对象使用输入和输出流克隆,必须序列化。

实现

  • 附件类(引用类型的成员变量,实现序列化)
/*
 * 附件类,这个是周报的附件
 */
public class Attachment implements Serializable{
	private static final long serialVersionUID = -799959163401886355L;
	private String name; // 名称
	public Attachment(String name) {
		super();
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}
  • 周报类(需要克隆的类,因为其中有引用类型的成员变量,因此需要实现序列化)
/*
 * 这个是周报类,这个类是实现接口Prototype这个接口的
 */
public class WeeklyLog implements Serializable {
	private static final long serialVersionUID = -8782492113927035907L;
	private String name; // 姓名
	private String date; // 日期
	private String content; // 内容
	private Attachment attachment; // 附件,是一个引用对象,这个只能实现浅克隆
	public WeeklyLog() {
		super();
	}
	/**
	 * 构造方法
	 */
	public WeeklyLog(String name, String date, String content) {
		super();
		this.name = name;
		this.date = date;
		this.content = content;
	}
	/**
	 * 提供一个clone方法,返回的是一个clone对象
	 */
	public WeeklyLog clone() {
		// 将对象写入到对象流中
		ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
		try {
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(
					arrayOutputStream); // 创建对象输出流
			objectOutputStream.writeObject(this); // 将这个类的对象写入到输出流中
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}

		// 将对象从流中读出
		ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(
				arrayOutputStream.toByteArray());
		WeeklyLog weeklyLog;
		try {
			ObjectInputStream objectInputStream = new ObjectInputStream(
					arrayInputStream);// 新建对象输入流
			weeklyLog = (WeeklyLog) objectInputStream.readObject(); // 读取对象从流中
			return weeklyLog;
		} catch (IOException | ClassNotFoundException e) {
			e.printStackTrace();
			return null;
		}

	}

}
  • 测试类
    • 从中可以看出其中的附件地址是不同的,如果一个对象的附件变量改变了,那么另外一个将保持不变,因此实现了深度克隆,是两个完全不同的对象
public class Client {
	public static void main(String[] args) throws CloneNotSupportedException {

		WeeklyLog p1 = new WeeklyLog("陈加兵", "第一周", "获得劳动模范的称号..."); // 创建一个对象
		Attachment attachment = new Attachment("消息");
		p1.setAttachment(attachment); // 添加附件
		WeeklyLog p2 = p1.clone();
		System.out.println(p1 == p2); // 判断是否正确
		p2.setName("Jack"); // 修改P2对象的内容
		p2.setDate("第二周");
		p2.setContent("工作认真.....");
		System.out.println(p2.getName());
		//返回false,可以看出这个是不同的地址,因此完成了深克隆
		System.out.println(p1.getAttachment() == p2.getAttachment());
	}
}

总结

  • 因为深度克隆使用的是将对象写入输入和输出流中的,因此需要实现序列化,否则将不能完成

总结

  1. 浅克隆只能克隆对象中的值类型,不能克隆有引用类型成员变量的对象
  2. 使用深度克隆:
    • 引用类型的成员变量的类必须实现序列化
    • 需要克隆的类必须实现序列化

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

Java transient关键字使用总结

哎,虽然自己最熟的是Java,但很多Java基础知识都不知道,比如transient关键字以前都没用到过,所以不知道它的作用是什么,今天做笔试题时发现有一题是关...

721
来自专栏数据小魔方

左手用R右手Python系列14——日期与时间处理

日期与时间格式数据处理通常在数据过程中要相对复杂一些,因为其不仅涉及到不同国家表示方式的差异,本身结构也较为复杂,在R语言和Python中,存在着不止一套方法来...

3267
来自专栏HTML5学堂

轻松但深入的学习闭包原理 —— 曾让几乎所有JS新手痛恨的知识

HTML5学堂-码匠:这或许是你看过的,最浅显易懂的一篇关于闭包原理的讲解! 闭包的官方定义 官方定义:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通...

3746
来自专栏PHP实战技术

你应该这个姿势学习PHP(1)

  应用场景:能防止sql的注入(当然并不完全是可以,我们可以使用pdo进行预处理然后方式sql的注入,安全不能只靠一种方式防止事情的发生)

52217
来自专栏LanceToBigData

JavaSE(十一)之异常处理详解

一、异常概述   在我们日常生活中,有时会出现各种各样的异常,例如:职工小王开车去上班,在正常情况下,小王会准时到达单位。但是天有不测风云,在小王去上班时,可能...

1909
来自专栏互联网杂技

前端--理解 Promise 的工作原理

Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的...

3726
来自专栏云霄雨霁

Java--类和对象之组合和继承

2347
来自专栏章鱼的慢慢技术路

牛客网_Go语言相关练习_判断&选择题(6)

反射最常见的使用场景是做对象的序列化(serialization,有时候也叫Marshal & Unmarshal)。

1011
来自专栏Golang语言社区

Golang语言之异常处理

在编写Go语言代码的时候,我们应该习惯使用error类型值来表明非正常的状态。作为惯用法,在Go语言标准库代码包中的很多函数和方法也会以返回error类型值来表...

34913
来自专栏编程坑太多

理解 JavaScript 的 async/await

1993

扫码关注云+社区

领取腾讯云代金券