原型模式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)]


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

被折叠的 条评论
为什么被折叠?



