JAVA设计模式-创建型模式-原型模式

原型模式

  • 定义
  • 解决问题
    • 浅复制,深复制
  • 原型模式结构
  • 案例分析
  • new 和 clone 效率比较

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

Prototype原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。

解决问题

它主要面对的问题是:“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有比较稳定一致的接口。

浅复制,深复制

讲到原型模式了,我们就不得不区分两个概念:深拷贝、浅拷贝。

浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。
被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。

深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。

如下图:JAVA设计模式-创建型模式-原型模式_第1张图片

原型模式结构

原型模式主要包含如下三个角色:
Prototype:抽象原型类。声明克隆自身的接口。
ConcretePrototype:具体原型类。实现克隆的具体操作。
Client:客户类。让一个原型克隆自身,从而获得一个新的对象。

我们都知道Object是祖宗,所有的Java类都继承至Object,而Object类提供了一个clone()方法,该方法可以将一个java对象复制一份,因此在java中可以直接使用clone()方法来复制一个对象。但是需要实现clone的Java类必须要实现一个接口:Cloneable.该接口表示该类能够复制且具体复制的能力,如果不实现该接口而直接调用clone()方法会抛出CloneNotSupportedException异常。

案例分析

我有person类和一个Pig类,person中引用pig,pig类是一个空类,继承Cloneable接口,重写object类的clone方法,具体结构如下

public class Person implements Cloneable,Serializable {

	private String name;

	private Integer age;

	private String sex;

	private Pig pig = new Pig();

	public Person() {
	}

	public Person(String name, Integer age, String sex) {
		this.name = name;
		this.age = age;
		this.sex = sex;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}

	public Pig getPig() {
		return pig;
	}

	public void setPig(Pig pig) {
		this.pig = pig;
	}

	@Override
	public String toString() {
		return "Person{" +
				"name='" + name + '\'' +
				", age=" + age +
				", sex='" + sex + '\'' +
				", pig=" + pig +
				'}';
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Person) {
			Person person = (Person) obj;
			return (person.name == name || (null != name && person.name.equals(name))) &&
					(person.age.equals(age) || (null != age && person.age.equals(age))) &&
					(person.sex == sex || (null != sex && person.sex.equals(sex))) &&
					(person.pig == pig || (null != pig && person.pig.equals(pig)));

		}
		return false;
	}
}

pig类

public class Pig implements Cloneable,Serializable {

	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}

下面进行测试clone功能

/**
* 测试clone模式
*/
public static void testOne(){
	Person personOne = new Person("javaboy",18,"男");
	log.info("personOne:{}",personOne);
	try {
		Person personTwo = (Person) personOne.clone();
		log.info("personTwo:{}",personTwo);
	} catch (CloneNotSupportedException e) {
		e.printStackTrace();
	}
}

执行结果如下

personOne:Person{name='javaboy', age=18, sex='男', pig=com.guhui.gof23.prototype.Pig@439f5b3d}
personTwo:Person{name='javaboy', age=18, sex='男', pig=com.guhui.gof23.prototype.Pig@439f5b3d}

可以看到,clone是成功的。但这只是一个浅复制,可以看下具体信息。

	/**
	 * 测试clone模式-浅复制
	 */
	public static void testTwo(){
		Person personOne = new Person("javaboy",18,"男");
		log.info("personOne:{}",personOne);
		try {
			Person personTwo = (Person) personOne.clone();

			log.info("personTwo:{}",personTwo);

			//查看原型实例和clone实例的hashCode
			log.info("personOne.hashCode():{},personTwo.hashCode():{}",personOne.hashCode(),personTwo.hashCode());

			//查看原型实例的pig和clone实例的pig 的hashCode
			log.info("personOne.Pig.hashCode:{},personTwo.Pig.hashCode:{}",personOne.getPig().hashCode(),personTwo.getPig().hashCode());

			//查看原型实例和clone实例 是否是同一个对象
			log.info("personOne == personTwo ??? {}",personOne == personTwo);

			//看看原型实例和clone实例 是否类型一样。
			log.info("personOne.class == personTwo.class ??? {}",personOne.getClass() == personTwo.getClass());

			//查看两个对象是否相等,抽象equest方法,比较内容
			log.info("personOne.class equest personTwo.class ??? {}",personOne.equals(personTwo));
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
	}

执行结果如下

personOne:Person{name='javaboy', age=18, sex='男', pig=com.guhui.gof23.prototype.Pig@439f5b3d}
personTwo:Person{name='javaboy', age=18, sex='男', pig=com.guhui.gof23.prototype.Pig@439f5b3d}
personOne.hashCode():565760380,personTwo.hashCode():6566818
personOne.Pig.hashCode:1134517053,personTwo.Pig.hashCode:1134517053
personOne == personTwo ??? false
personOne.class == personTwo.class ??? true
personOne.class equest personTwo.class ??? true

可以看到,虽然两个的hashCode不相等,不是同一个对象,但是通过equest比较,确是相等的,两个对象的pig属性的hashCode值也相等。证明两个对象的属性指向同一个地址

下面我们看看深复制。深复制我利用序列化来执行。所以person和pig类都实现了Serializable接口

	/**
	 * 测试clone模式-深复制
	 */
	public static void testThree(){
		Person personOne = new Person("javaboy",18,"男");
		log.info("personOne:{}",personOne);
		try {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);
			oos.writeObject(personOne);
			byte[] bytes = bos.toByteArray();

			ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
			ObjectInputStream ois = new ObjectInputStream(bis);

			Person personTwo = (Person) ois.readObject();

			log.info("personTwo:{}",personTwo);

			log.info("personOne.hashCode():{},personTwo.hashCode():{}",personOne.hashCode(),personTwo.hashCode());
			log.info("personOne.Pig.hashCode:{},personTwo.Pig.hashCode:{}",personOne.getPig().hashCode(),personTwo.getPig().hashCode());
			log.info("personOne == personTwo ??? {}",personOne == personTwo);
			log.info("personOne.class == personTwo.class ??? {}",personOne.getClass() == personTwo.getClass());
			log.info("personOne.class equest personTwo.class ??? {}",personOne.equals(personTwo));
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

结果如下

personOne:Person{name='javaboy', age=18, sex='男', pig=com.guhui.gof23.prototype.Pig@439f5b3d}
personTwo:Person{name='javaboy', age=18, sex='男', pig=com.guhui.gof23.prototype.Pig@484b61fc}
personOne.hashCode():1029991479,personTwo.hashCode():1174290147
personOne.Pig.hashCode:1134517053,personTwo.Pig.hashCode:1212899836
personOne == personTwo ??? false
personOne.class == personTwo.class ??? true
personOne.class equest personTwo.class ??? false

从执行结果,可以看到,两个对象的hashCode值不同,属性pig的hashCode也不相等,并且通过equest的判断,得到结果为false。证明两个对象的属性指向的地址不是同一个,证明深复制成功。

new 和 clone 效率比较

下面我们看下new和clone效率比较问题。由于clone操作不会调用构造函数,是基于内存中的数据库copy,那是不是clone的效率一定比new高呢?我们测试一下。

我创建了一个空类,里面没有属性,只有一个空的构造函数和重写的clone函数

public class Laptop implements Cloneable {

	public Laptop() {	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}

然后我们进行测试

public class LaptopTime {

	public static void main(String[] args) {
		//创建多少个对象
		int size = 10000 * 10000;
		try {
			testNew(size);
			testClone(size);
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
	}

	public static void testNew(int size){
		long start = System.currentTimeMillis();
		for(int i=0;i

案例中循环了一亿次,从而new了一亿个对象和clone了一亿个对象。执行结果如下

new的方式创建耗时:9
clone的方式创建耗时:2483

可以看到,clone消耗的时间远远超过new消耗的时间,达到了惊人的276倍。
显然jvm的开发者也意识到通过new方式来生成对象占据了开发者生成对象的绝大部分,所以对于利用new操作生成对象进行了优化。

然后我们测试一下深复制的效率
对Laptop类进行稍微的修改,代码如下:

public class Laptop implements Cloneable {

	private Pig pig;
	private Person person;
	private Pig pig2;
	private Person person2;


	public Laptop() {
		pig = new Pig();
		person = new Person("javaboy",18,"男");
		pig2 = new Pig();
		person2 = new Person("javaboy",18,"男");
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		Laptop clone = (Laptop) super.clone();
		clone.pig = (Pig) pig.clone();
		clone.person = (Person) person.clone();
		clone.pig2 = (Pig) pig2.clone();
		clone.person2 = (Person) person2.clone();
		return clone;
	}
}

在laptop中新加四个对象属性,在clone方法中实现深复制,执行效果如下

new的方式创建耗时:4779
clone的方式创建耗时:4884

同样一亿次循环创建对象。两者之间的效率已经相当接近了。
结论: 可以发现,“深复制”并没有clone方法的效率!相反,这种方法此时比通过new构造对象的方法效率还低。

同样,我们把laptap中clone方法改为浅复制

@Override
protected Object clone() {
	return super.clone();
}

属性不改动,再次执行,结果如下

new的方式创建耗时:4576
clone的方式创建耗时:913

可以看出,clone的效率要高于new。

至此,基本可以确定,clone方法只有在进行 复杂 的浅复制时效率才会明显高于new构造方式,但由于此时的clone本质上依旧是浅复制,因此需要注意引用的指向问题,避免错误更改关联的信息值。

代码案例文件:
http://pvyob7cem.bkt.clouddn.com/prototype.rar

你可能感兴趣的:(JAVA,设计模式)