原型模式PrototypeMode

本文详细介绍了Java中的原型模式,包括对象复制的多种方式,浅拷贝和深拷贝的概念,以及如何通过重写clone方法实现深拷贝。此外,还探讨了克隆单例的悖论以及处理循环依赖的克隆问题。最后,提到了原型注册表作为对象复用的一种策略。

原型模式PrototypeMode

1.概述

原型模式,顾名思义即以一个对象为原型进行复刻或者直接说是克隆。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oDtOXeY6-1631194787332)(F:\LocalTyproPictrue\prototype.jpg)]
请添加图片描述

2.前言

原型模式即对对象进行复制来创建新的对象

在Java的学习中,

创建对象一般都需要调用构造函数

先来复习一下创建对象的几种不同方式

1.通过new关键字Person zhangsan = new Person()
2.通过反射的Class对象Person lisi = zhangsan.getClass().newInstance();
3.通过Constructor对象创建先通过Class对象拿到Construtor,然后再newInstance()
4.反序列化创建对象把实现序列化接口的对象从硬盘文件读出
5.实现Clonable接口实现Clonable接口

其中反序列化和clone不会调用构造函数

3.深拷贝和浅拷贝的概念

基本上在所有的高级语言中,对象的复制都存在深浅拷贝的概念

以Java为例,默认Object的clone方法就是浅拷贝(只为基础数据类型开辟空间)

public class Address implements Cloneable{

	private String City;
	
	private String Province;
	
	private String Country;

	
	
	public Address(String city, String province, String country) {
		super();
		City = city;
		Province = province;
		Country = country;
	}
    
    //省略Getter&Setter方法

    
    //使用Object提供的克隆方法
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();
	}
	
}

public static void main(String[] args) throws CloneNotSupportedException {
		Address a1 = new Address("新余", "江西", "中国");
    	//引用
		Address a2 = a1;
    	//克隆
		Address a3 = (Address) a1.clone();
		System.out.println(a2==a1);
		System.out.println(a3==a1);
	}
//-----------------------------结果
true
false

向类中添加引用对象,创建一个新的类Person

public class Person implements Cloneable {
	
	private String name;
	
	private String id;
	
    //在类中存在一个引用的变量address
	private Address address;

	//省略Getter&Setter方法
	
	public Person(String name, String id, Address address) {
		this.name = name;
		this.id = id;
		this.address = address;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", id=" + id + ", address=" + address + "]";
	}

    //使用Object类的clone()
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
	
}

测试:

	@Test
	public void Test() throws CloneNotSupportedException {
		Address a1 = new Address("新余", "江西", "中国");
		Person zhangsan = new Person("张三", "20182109", a1);
		Person lisi = (Person) zhangsan.clone();
        
        //是否是不同对象
		System.out.println(zhangsan==lisi);
		//查看两个对象的引用变量是否是同一个Address对象
        System.out.println(zhangsan.getAddress()==lisi.getAddress());
	}

结果:

false
true

从结果可以看出Object的clone方法并没有去复制一个不一样的引用变量(或者说引用对象)

所以Object的克隆方法为浅拷贝

4.重写clone方法实现深拷贝

以上诉代码为例,要实现Person类的深拷贝很简单,既然引用对象没有被复制,那么再调用引用对象的clone()方法重新克隆一个就好了

只需要改一下从写的clone方法

	@Override
	protected Person clone() throws CloneNotSupportedException {
		Person person =  (Person) super.clone();
        //将克隆生成对象的引用对象设定为当前对象的引用的克隆
        //二次克隆
        //如果克隆对象的引用对象中还有引用对象,则还需重复克隆引用对象
        //就像套娃一样,很麻烦
		person.setAddress((Address)this.address.clone());
		return person;
	}

测试:

	@Test
	public void Test() throws CloneNotSupportedException {
		Address a1 = new Address("新余", "江西", "中国");
		Person zhangsan = new Person("张三", "20182109", a1);
		Person lisi = (Person) zhangsan.clone();
		System.out.println(zhangsan==lisi);
		System.out.println(zhangsan.getAddress()==lisi.getAddress());
	}

结果:

false
false

现在就完成了深拷贝,

两个对象都是相互独立的了

5.对象生成的效率

设计一个测试,对比一下二者的效率

/**
	 * 测试克隆和构造器生成对象的效率
	 * @throws CloneNotSupportedException 
	 * @throws InterruptedException 
	 * 前提:为了控制变量,当前二者(Address,Person)的构造方法里没有额外的操作
	 */
	@Test
	public void CompareToCloneAndConstructor() throws CloneNotSupportedException, InterruptedException {
		//要被克隆的原型:张三
		Person Clone_Sample = new Person("张三", "123", new Address("c1", "p1", "c1"));
		
		long start = System.currentTimeMillis();
		//通过构造方法创建1000个Person对象
		for(int i = 0;i<1000;i++) {
			Address address = new Address("c"+i,"p"+i,"c"+i);
			new Person("No."+i, "number:"+i, address);
			Thread.sleep(2);
		}
		
		long end = System.currentTimeMillis();
		
		System.out.println((end-start));
		
		long start2 = System.currentTimeMillis();
		//通过克隆方法创造一千个克隆对象
		for(int j= 0;j<1000;j++) {
			Person cloner = Clone_Sample.clone();
			Thread.sleep(2);
		}
		
		long end2 = System.currentTimeMillis();
		
		System.out.println((end2-start2));
	}
2699
2642

可以看出二者的效率并没有差多少,拷贝只比构造快了50毫秒

但是

一些需要拷贝的复杂对象的构造函数里可能有复杂的操作

这里就人为在构造函数里搞一点复杂操作吧Doge

public Address(String city, String province, String country) {
    	//直接在Address里进行循环浮点运算Doge
		for(int i = 0;i<100;i++) {
			for(int j = 0;j<100;j++) {
				Random random = new Random();
				double d1 = random.nextDouble();
				double d2 = random.nextDouble();
				System.out.println(d1*d2);
			}
		}
		
		City = city;
		Province = province;
		Country = country;
}
public Person(String name, String id, Address address) {
    	//直接在Address里进行循环浮点运算Doge
		for(int i = 0;i<100;i++) {
			for(int j = 0;j<100;j++) {
				Random random = new Random();
				double d1 = random.nextDouble();
				double d2 = random.nextDouble();
				System.out.println(d1*d2);
			}
		}
		this.name = name;
		this.id = id;
		this.address = address;
}

再来测试一下消耗的时间

@Test
	public void CompareToCloneAndConstructor() throws CloneNotSupportedException, InterruptedException {
		
		Person Clone_Sample = new Person("张三", "123", new Address("c1", "p1", "c1"));
		
		long start = System.currentTimeMillis();
		
		for(int i = 0;i<100;i++) {
			Address address = new Address("c"+i,"p"+i,"c"+i);
			Person p1 = new Person("No."+i, "number:"+i, address);
			
		}
		
		long end = System.currentTimeMillis();
		
		System.out.println((end-start));
		
		long start2 = System.currentTimeMillis();
		
		for(int j= 0;j<100;j++) {
			Person cloner = Clone_Sample.clone();
		}
		
		long end2 = System.currentTimeMillis();
		
		System.out.println((end2-start2));
	}

结果很Σ(っ °Д °;)っ

//----------省略上面的浮点输出
11148
0

很明显,对于复杂的对象,克隆的效率比构造函数不知道高到哪里去了

6.一个悖论

能不能克隆单例?

直接开干

public class TheSingleTon implements Cloneable{
	
    //让单例实现Cloneable接口
	private static TheSingleTon theone = new TheSingleTon();
	
	private TheSingleTon() {
		
	}
	
	public static TheSingleTon getInstance() {
		return theone;
	}

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

测试:

/**
	 * @param
	 * 测试是否能在单例模式下克隆单例对象
	 * @throws CloneNotSupportedException 
	 */
	@Test
	public void CloneSingleTon() throws CloneNotSupportedException {
		TheSingleTon single = TheSingleTon.getInstance();
         //通过克隆单例对象创建对象
		TheSingleTon doubles = (TheSingleTon) single.clone();
		TheSingleTon third = TheSingleTon.getInstance();
		System.out.println(single==doubles);
		System.out.println(third==single);
	}

结果:

false
true

结果证明克隆可以破坏单例模式

(其实通过反序列化和反射也可以)

其实单例和原型就是相悖的概念,没有东西既是单例又可以以之为原型进行复制

7.一个启发,循环依赖的克隆

那么对于循环依赖,我们能不能进行克隆呢?

先写一个例子:

/**
 * 
 * @author ASUS
 * 尝试克隆循环依赖
 */
public class CircleReference {
	public static void main(String[] args) throws CloneNotSupportedException {
		A a = new A();
		B b = new B();
		a.setB(b);
		b.setA(a);
		A acopy = (A) a.clone();
		B bcopy = (B)b.clone();
		System.out.println(a==acopy);
		System.out.println(b==bcopy);
	}

}
class A implements Cloneable{
	private B b;

	public B getB() {
		return b;
	}

	public void setB(B b) {
		this.b = b;
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		A result = (A) super.clone();
		result.setB((B)this.b.clone());
		return result;
	}
	
	
	
}
class B implements Cloneable{
	private A a;

	public A getA() {
		return a;
	}

	public void setA(A a) {
		this.a = a;
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		B result = (B) super.clone();
		result.setA((A)a.clone());
		return result;
	}
	
}

毫无疑问:两个clone()方法调来调去,铁定是栈溢出

Exception in thread "main" java.lang.StackOverflowError
	at CloneMode.A.clone(CircleReference.java:34)
	at CloneMode.B.clone(CircleReference.java:56)
	at CloneMode.A.clone(CircleReference.java:35)
	at CloneMode.B.clone(CircleReference.java:56)
	.......................................

是否可以解决

应该是可以解决,但不能使用Java提供的cloneable接口,

且需要建立一套缓存,

看来十分困难

8.优缺点

  • 你可以克隆对象, 而无需与它们所属的具体类相耦合。

  • 你可以克隆预生成原型, 避免反复运行初始化代码。

  • 你可以更方便地生成复杂对象。

  • 你可以用继承以外的方式来处理复杂对象的不同配置。

  • 克隆包含循环引用的复杂对象可能会非常麻烦。

9.原型注册表

简而言之,就是用一个Map放一些典型需要克隆的对象,然后在需要的时候取出克隆

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-33dIV5I8-1631194787335)(F:\LocalTyproPictrue\structure-prototype-cache.png)]

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

罗马苏丹默罕默德

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

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

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

打赏作者

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

抵扣说明:

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

余额充值