0.参考资料
1.概述
- 使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。—《设计模式》GoF
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,
无需知道如何创建的细节
- 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建
的对象通过请求原型对象拷贝它们自己来实施创建,如 对象.clone()
- 核心:
1.1动机
- 在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。
- 如何应对这种变化?如何向"客户程序(使用这些对象的程序)" 隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?
1.2结构
- ![Q8_%P9MUY7FU8$C5Y@Q2HXW.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628687578262-4354dd23-1a10-4f31-b502-f397def427cb.png#clientId=ude33fbc5-3439-4&from=paste&height=283&id=u2328ddb3&margin=%5Bobject%20Object%5D&name=Q8_%25P9MUY7FU8%24C5Y%40Q2HXW.png&originHeight=565&originWidth=1217&originalType=binary&ratio=1&size=92342&status=done&style=none&taskId=u7cb2698c-bbd1-49c7-91be-7a4305c5776&width=608.5)
2.要点总结
- 宏观上
- Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些 “易变类”拥有“稳定的接口”。
- Prototype模式对于“如何创建易变类的实体对象”采用”原型克隆”的方法来做,它使得我们可以非常灵活地动态创建“拥有某些稳定接口”的新对象一-所需 工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方Clone。
- Prototype模式中的Clone方法可以利用某些框架中的序列化来实现深拷贝。
微观代码上
public class Demo1 { public static void main(String[] args) {
Sheep mySheep = new Sheep("tom", 10);
Sheep sheep1 = new Sheep(mySheep.getName(), mySheep.getAge());
Sheep sheep2 = new Sheep(mySheep.getName(), mySheep.getAge());
Sheep sheep3 = new Sheep(mySheep.getName(), mySheep.getAge());
System.out.println(mySheep);
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
}
}
@Data @AllArgsConstructor public class Sheep {
String name;
Integer age;
}
<a name="Teio7"></a>
## 分析
- 优缺点:
- 优点是比较好理解,简单易操作。
- 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂 时,效率较低
- 总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活
- 改进思路
- Java中Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以 将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口**Cloneable**, 该接口表示该类能够复制且具有复制的能力 => 原型模式
<a name="ljftI"></a>
# 4.使用模式
<a name="y8ceq"></a>
#### 方案
- 使用**原型模式**改进传统方式,让程序具有更高的效率和扩展性。
<a name="t4ODn"></a>
#### 类图
...
<a name="vHb6W"></a>
#### 代码
<a name="HROyd"></a>
##### 版本1: 简单的无引用类型属性(不包括String和包装类)
- 代码
```java
public class Demo2 {
public static void main(String[] args) {
Sheep mySheep = new Sheep("tom", 10);
Sheep sheep1 = (Sheep)mySheep.clone();
Sheep sheep2 = (Sheep)mySheep.clone();
Sheep sheep3 = (Sheep)mySheep.clone();
System.out.println(mySheep);
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(mySheep == sheep1);
System.out.println(mySheep == sheep3);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Sheep implements Cloneable{
String name;
Integer age;
@Override
protected Object clone() {
Sheep temp = null;
try {
temp = (Sheep)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return temp;
}
}
- 测试结果
Sheep(name=tom, age=10)
Sheep(name=tom, age=10)
Sheep(name=tom, age=10)
Sheep(name=tom, age=10)
false
false
- 结论
- clone() 返回的是一个属性的值与原型一样的对象(Java中只有值传递).
- 这两个对象的空间指向不一样(== 为false)
版本2: 属性中有普通的引用类型
- 代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Sheep implements Cloneable {
String name;
Integer age;
Address address;
@Override
protected Object clone() {
Sheep temp = null;
try {
temp = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return temp;
}
}
// 新增引用类型属性
@Data
@AllArgsConstructor
@ToString
public class Address implements Cloneable{
private Provices provices;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Data
@AllArgsConstructor
@ToString
class Provices implements Cloneable{
String provicesName;
City city;
}
@Data
@AllArgsConstructor
@ToString
class City implements Cloneable{
String cityName;
}
// 测试类
public class Demo2 {
public static void main(String[] args) {
Sheep mySheep = new Sheep("tom", 10, new Address(new Provices("江西" ,new City("抚州"))));
Sheep sheep1 = (Sheep)mySheep.clone();
Sheep sheep2 = (Sheep)mySheep.clone();
Sheep sheep3 = (Sheep)mySheep.clone();
System.out.println("修改前");
System.out.println(mySheep);
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
sheep3.getAddress().getProvices().setCity(new City("九江"));
System.out.println("修改后");
System.out.println(mySheep);
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(mySheep == sheep1);
System.out.println(mySheep == sheep3);
}
}
- 测试结果
修改前
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
修改后
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=九江))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=九江))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=九江))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=九江))))
false
false
- 结论
- 对于属性中有引用类型的对象的 clone(), 因为原始的 clone()是值拷贝, 虽然确实已经开辟了新的堆空间( == 判断为false), 但是堆空间中的值引用还是值拷贝--即和原型一致.改变任意一个实例对象的引用属性, 其它实例对象包括原型的对应的引用属性都会被改变(即引用一致) -->浅拷贝
- 想要完善上面的问题, 应进行[深拷贝](#KZycN).
- 与深拷贝的对比
5.经典使用
5.1Spring中原型bean的创建,就是原型模式的应用
5.2源码分析
1. beans.xml配置
:::info
2. 测试类
{
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取monster[通过id获取monster]
Object bean = applicationContext.getBean("id01");
System.out.println("bean" + bean)
}
3. getBean(...)方法
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628691152713-8affd658-30d6-437e-980d-acd4f3d86481.png#clientId=ude33fbc5-3439-4&from=paste&height=52&id=u08b0e289&margin=%5Bobject%20Object%5D&name=image.png&originHeight=104&originWidth=613&originalType=binary&ratio=1&size=8674&status=done&style=none&taskId=u5420ac31-f457-4d77-a671-1e5b8e7a0bd&width=306.5)
4. 底层调用, 创造原型对象
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628691160576-0833e1ba-238e-463e-b896-7d2d31146c1b.png#clientId=ude33fbc5-3439-4&from=paste&height=218&id=ufba44bbc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=435&originWidth=822&originalType=binary&ratio=1&size=52957&status=done&style=none&taskId=u707bdaaf-6163-4e0a-b011-dbc111066d5&width=411)
6.Java对象拷贝中的深拷贝和浅拷贝
6.1对象拷贝
6.1.1示例
Teacher teacher = new Teacher("Swift",26);
Teacher otherteacher = (Teacher)teacher.clone();
System.out.println(teacher);
System.out.println(otherteacher);
blog.Teacher@355da254
blog.Teacher@4dc63996
6.1.2概述
- 由输出结果可以看出,它们的地址是不同的,也就是说创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量,这就叫做对象拷贝。
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628755782248-d13dfdd4-d7bf-494e-8842-5ad7b65276f5.png#clientId=u0e86ce5a-55c7-4&from=paste&height=276&id=uf091e574&margin=%5Bobject%20Object%5D&name=image.png&originHeight=552&originWidth=913&originalType=url&ratio=1&size=30849&status=done&style=none&taskId=uc20ff949-b1ba-42d4-ac40-bbc4b469adc&width=457)
6.2浅拷贝
6.2.1示例
- 代码
public class ShallowCopy {
public static void main(String[] args) throws CloneNotSupportedException {
// 原型中的引用对象
Teacher teacher = new Teacher();
teacher.setName("Delacey");
teacher.setAge(29);
// 原型对象
Student2 student1 = new Student2();
student1.setName("Dream");
student1.setAge(18);
student1.setTeacher(teacher);
// 对原型对象进行对象(浅拷贝)
Student2 student2 = (Student2) student1.clone();
System.out.println("拷贝后");
System.out.println(student2.getName());
System.out.println(student2.getAge());
System.out.println(student2.getTeacher().getName());
System.out.println(student2.getTeacher().getAge());
// 修改老师的信息
teacher.setName("Jam");
System.out.println("修改老师的姓名为Jam后-------------");
System.out.println(student1.getTeacher().getName());
System.out.println(student2.getTeacher().getName());
}
}
@Data
class Teacher implements Cloneable {
private String name;
private int age;
}
@Data
class Student2 implements Cloneable{
private String name;
private int age;
private Teacher teacher;
@Override
public Object clone() throws CloneNotSupportedException {
Object object = super.clone();
return object;
}
}
- 测试结果
拷贝后
Dream
18
Delacey
29
修改老师的信息后-------------
Jam
Jam
- 结果分析
- 两个引用student1和student2指向不同的两个对象,但是两个引用student1和student2中的两个teacher引用指向的是同一个对象,所以说明是浅拷贝。
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628756240624-7ab3acf3-5a33-416b-8433-565c925a01b1.png#clientId=uccac0922-6a1b-4&from=paste&height=385&id=u15830f9a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=769&originWidth=1394&originalType=url&ratio=1&size=73272&status=done&style=none&taskId=ud12c3239-b357-42dd-9730-cbdab28ec99&width=697)
6.2.2概述
- 原型对象实现cloneable 并重写 clone().
- 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。"里面的对象“会在原来的对象和它的副本之间共享。
- 简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象
6.3深拷贝
6.2.1示例(Clone()方法)
- 代码
public class DeepCopy {
public static void main(String[] args) throws Exception {
//原型对象中的引用属性
Teacher2 teacher = new Teacher2();
teacher.setName("Delacey");
teacher.setAge(29);
//原型对象
Student3 student1 = new Student3();
student1.setName("Dream");
student1.setAge(18);
student1.setTeacher(teacher);
//深拷贝对象
Student3 student2 = (Student3) student1.clone();
System.out.println("拷贝后");
System.out.println(student2.getName());
System.out.println(student2.getAge());
System.out.println(student2.getTeacher().getName());
System.out.println(student2.getTeacher().getAge());
System.out.println("修改老师的信息后-------------");
// 修改老师的信息
teacher.setName("Jam");
System.out.println(student1.getTeacher().getName());
System.out.println(student2.getTeacher().getName());
}
}
@Data
class Teacher2 implements Cloneable {
private String name;
private int age;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Data
class Student3 implements Cloneable {
private String name;
private int age;
private Teacher2 teacher;
@Override
public Object clone() throws CloneNotSupportedException {
// 浅复制时:
// Object object = super.clone();
// return object;
// 改为深复制:
Student3 student = (Student3) super.clone();
// 本来是浅复制,现在将Teacher对象复制一份并重新set进来
student.setTeacher((Teacher2) student.getTeacher().clone());
return student;
}
}
- 测试结果
拷贝后
Dream
18
Delacey
29
修改老师的信息后-------------
Jam
Delacey
- 结果分析
- 两个引用student1和student2指向不同的两个对象,两个引用student1和student2中的teacher属性引用指向的是两个对象,但对teacher对象的修改只能影响student1对象,所以说是深拷贝。
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628756505682-38330bc8-d9cb-4403-9640-81516c938059.png#clientId=uccac0922-6a1b-4&from=paste&height=367&id=u8d2b04a3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=734&originWidth=1327&originalType=url&ratio=1&size=65490&status=done&style=none&taskId=uf29b1ee6-e010-44e4-ae65-855c394e25d&width=664)
6.2.2概述
- 深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
- 简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
6.2.3深拷贝的两种方法
1. 实现cloneable 接口的clone()方法: [简单的clone()实现浅拷贝](#ljftI) 可以看出, 若想用clone()实现深拷贝, 则原型中的所有(引用类型)属性及其属性类中的(引用类型)属性都应该实现cloneable 接口并重写clone()方法(层层嵌套), 并且在逐层往上的clone()中进行调用对应属性的clone(). 即多层克隆问题, 非常不方便. 不建议使用.
1. 实现Serializable 接口序列化: 可以解决多层克隆的问题. 要求所有的对象对实现 Serializable 接口, 以序列化写入/读取到内存中.
6.2.4使用深拷贝解决克隆羊的问题
- 代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Sheep implements Serializable {
String name;
Integer age;
Address address;
// 测试方法
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 原型对象
Sheep prototype = new Sheep("tom", 10, new Address(new Provices("江西", new City("抚州"))));
Sheep sheep1 = (Sheep)deepClone(prototype);
Sheep sheep2 = (Sheep)deepClone(prototype);
Sheep sheep3 = (Sheep)deepClone(prototype);
System.out.println("修改前");
System.out.println(prototype);
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
sheep1.getAddress().setProvices(new Provices("北京", new City("紫禁城")));
sheep3.getAddress().getProvices().setCity(new City("九江"));
System.out.println("修改后");
System.out.println(prototype);
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(prototype == sheep1);
System.out.println(prototype == sheep3);
}
// 深拷贝方法
public static Object deepClone(Sheep sheep) throws IOException, ClassNotFoundException {
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(sheep);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
- 测试结果
修改前
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
修改后
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=北京, city=City(cityName=紫禁城))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=九江))))
false
false
- 分析
- 每一个拷贝出来的都是一个个体, 包括其内部包含的引用属性, 与原型对象和其它的拷贝对象互不干扰.
- 好处
- 只需要每个属性实现 序列化Serializable接口(类比clone()方法中, 都需要实现cloneable接口), 而不再需要重写clone(), 不用处理多层克隆问题.