Appearance
原型模式
概述
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象
结构
原型模式包含如下角色:
- 抽象原型类:规定了具体原型对象必须实现的clone()方法
- 具体原型类:实现抽象原型类的clone()方法,它是可被复制的对象
- 访问类:使用具体原型类中的clone方法来复制新的对象
实现
原型模式的克隆分为浅克隆和深克隆
- 浅克隆:创建一个新对象,新对象的属性和原来对象的完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不在指向原有对象地址
Java中的Object类中提供了clone()方法来实现浅克隆。Cloneable接口是上面类图中的抽象原型类(Prototype),而实现了Cloneable接口的子实现类就是具体的原型类(Realizetype),代码如下:
具体原型类(Realizetype)
java
package com.huangjiliang.design.heima.create.prototype.demo1;
public class Realizetype implements Cloneable{
public Realizetype() {
System.out.println("具体原型创建成功!");
}
@Override
protected Realizetype clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功");
return (Realizetype)super.clone();
}
}
测试类(Client)
java
package com.huangjiliang.design.heima.create.prototype.demo1;
/**
* 客户端-演示原型模式-创建
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Realizetype realizetype = new Realizetype();
Realizetype clone = realizetype.clone();
// 创建的对象是否和克隆出来的对象一致:false
System.out.println("创建的对象是否和克隆出来的对象一致:" + (realizetype == clone));
}
}
案例
用原型模式生产“三好学生”奖状
同一个学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式克隆出来多个“三好学生”奖状出来,然后再修改奖状上的学生名字即可。其类型图如下:
获奖类
java
package com.huangjiliang.design.heima.create.prototype.demo2;
/**
* 三好学生奖状
*/
public class Citation implements Cloneable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Citation() {
System.out.println("具体原型创建成功!");
}
@Override
protected Citation clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功");
return (Citation)super.clone();
}
public void show() {
System.out.println(name + "获得了三好学生奖状!");
}
}
测试类
java
package com.huangjiliang.design.heima.create.prototype.demo2;
/**
* 客户端-演示原型模式-创建
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Citation r1 = new Citation();
r1.setName("黄继良");
r1.show();
Citation r2 = r1.clone();
r2.setName("刘德华");
r2.show();
}
}
使用场景
- 对象的创建非常复杂,可以使用原型模式快捷的创建对象
- 性能和安全要求比较高
浅克隆
将上面的“三好学生”奖状的案例中的Citation类的name属性修改为Student类型的属性。代码如下
学生类
java
package com.huangjiliang.design.heima.create.prototype.demo3;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 学生类
*/
@Data
@Accessors(chain = true)
public class Student {
private String id;
private String name;
private String studentNum;
}
三好学生奖状
java
package com.huangjiliang.design.heima.create.prototype.demo3;
import lombok.Data;
/**
* 三好学生奖状
*/
@Data
public class Citation implements Cloneable{
private Student student;
public Citation() {
System.out.println("具体原型创建成功!");
}
@Override
protected Citation clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功");
return (Citation)super.clone();
}
public void show() {
System.out.println(student.getName() + "获得了三好学生奖状!");
}
}
测试类
java
package com.huangjiliang.design.heima.create.prototype.demo3;
/**
* 客户端-深克隆
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Citation r1 = new Citation();
Student student1 = new Student();
student1.setName("黄继良").setId("001").setStudentNum("1000");
r1.setStudent(student1);
Citation r2 = r1.clone();
Student student2 = r2.getStudent();
student2.setName("刘德华");
r1.show();// 刘德华获得了三好学生奖状! 【r1引用的student原本是黄继良,而被student2修改了】
r2.show();// 刘德华获得了三好学生奖状!
// student1和student2是否是同一个对象:true【证明引用的是同一个对象】
System.out.println("student1和student2是否是同一个对象:" + (student1 == student2));
}
}
- student1和student2是同一个对象,所以student2修改name属性之后,两个Citation对象中显示都是student2修改后的name,这就是浅克隆的效果。对具体原型类(Citation)中国红的引用类型的属性进行引用的复制。这种情况需要使用深克隆,而进行深克隆需要使用对象流。
深克隆
1、创建序列化工具类
java
package com.huangjiliang.design.heima.create.prototype.demo4;
import java.io.*;
/**
* 类名 SerialCloneUtils
* 描述 序列化克隆工具
*/
public class SerialCloneUtils{
/**
* 使用ObjectStream序列化实现深克隆
* @return Object obj
*/
public static <T extends Serializable> T deepClone(T t) throws CloneNotSupportedException {
// 保存对象为字节数组
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try(ObjectOutputStream out = new ObjectOutputStream(bout)) {
out.writeObject(t);
}
// 从字节数组中读取克隆对象
try(InputStream bin = new ByteArrayInputStream(bout.toByteArray())) {
ObjectInputStream in = new ObjectInputStream(bin);
return (T)(in.readObject());
}
} catch (IOException | ClassNotFoundException e) {
CloneNotSupportedException cloneNotSupportedException = new CloneNotSupportedException();
e.initCause(cloneNotSupportedException);
throw cloneNotSupportedException;
}
}
}
2、创建序列化抽象类
java
package com.huangjiliang.design.heima.create.prototype.demo4;
import java.io.Serializable;
/**
* 类名 SerialClone
* 描述 序列化克隆类,只有继承该类,就可以实现深克隆
*/
public class SerialClone implements Cloneable, Serializable {
private static final long serialVersionUID = 5794148504376232369L;
@Override
public Object clone() throws CloneNotSupportedException {
return SerialCloneUtils.deepClone(this);
}
}
3、三好学生类修改-继承自定义克隆类
java
package com.huangjiliang.design.heima.create.prototype.demo4;
import lombok.Data;
/**
* 三好学生奖状
*/
public class Citation extends SerialClone {
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public Citation() {
System.out.println("具体原型创建成功!");
}
public void show() {
System.out.println(student.getName() + "获得了三好学生奖状!");
}
}
4、学生类修改-继承自定义克隆类
java
package com.huangjiliang.design.heima.create.prototype.demo4;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 学生类
*/
public class Student extends SerialClone {
private static final long serialVersionUID = 1L;
private String id;
private String name;
private String studentNum;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getStudentNum() {
return studentNum;
}
public void setStudentNum(String studentNum) {
this.studentNum = studentNum;
}
}
5、测试类
java
package com.huangjiliang.design.heima.create.prototype.demo4;
/**
* 客户端-深克隆
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Citation r1 = new Citation();
Student student1 = new Student();
student1.setName("黄继良");
r1.setStudent(student1);
Citation r2 = (Citation)r1.clone();
Student student2 = r2.getStudent();
student2.setName("刘德华");
r1.show();// 黄继良获得了三好学生奖状!
r2.show();// 刘德华获得了三好学生奖状!
// student1和student2是否是同一个对象:false
System.out.println("student1和student2是否是同一个对象:" + (student1 == student2));
}
}
- 测试后发现student1和student2并不是指向同一个引用地址
- 使用此方式使用Lombok的@Data注解会报错。原因未知
- 还有其他深克隆方式-如:使用Json工具类进行序列化等