原型模式:属于创建型模式使你能够复制已有对象, 而又无需使代码依赖它们所属的类。
原型模式的UML类图
角色:原型 (Prototype) 接口将对克隆方法进行声明。 在绝大多数情况下, 其中只会有一个名为 clone克隆的方法。 具体原型 (Concrete Prototype) 类将实现克隆方法。 客户端 (Client) 可以复制实现了原型接口的任何对象。
原型模式将克隆过程委派给被克隆的实际 对象 。 模式为所有 支持克隆 的对象声明了一个通用 接口(Cloneable接口), 该接口让你能够克隆对象, 同时又无需将代码和对象所属类耦合。 通常情况下, 这样的接口中仅包含一个 克隆方法(clone方法)。 作用:创建一系列不同类型的对象并不同的方式对其进行配置。 如果所需对象与预先配置的对象相同, 那么你只需克隆原型即可, 无需新建一个对象。
浅拷贝代码
Video类
import java.util.Date;
/**
* @author jdw
* @date 2021/8/19-14:18
*/
//实现克隆的步骤 : 1.实现Cloneable接口 2.重写clone()方法
//提供了一个叫做 Video 的类,并且实现了支持此类的对象 能够被克隆的操作↑ (即该类的对象 可以被克隆)
public class Video implements Cloneable{
private String name;
private Date createTime;
@Override
protected Object clone() throws CloneNotSupportedException { //父类浅拷贝
return super.clone(); //调用了父类的clone()
// protected native Object clone() throws CloneNotSupportedException; native是java提供直接操作c++ 控制内存 已经封装好的
}
public Video() {
}
public Video(String name, Date createTime) {
this.name = name;
this.createTime = createTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", createTime=" + createTime +
'}';
}
}
Client类
import java.util.Date;
/**
* @author jdw
* @date 2021/8/19-14:58
*/
//浅拷贝
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
//原型对象
Date date = new Date();
Video video1 = new Video("java视频", date);
System.out.println("video1 = " + video1);
System.out.println("video1.hash = " + video1.hashCode());
// v1 克隆出 v2
// Video video2 = new Video("java视频", date); //以前:
Video video2 = (Video) video1.clone();
System.out.println("video2 = " + video2);
System.out.println("video2.hash = " + video2.hashCode());
System.out.println("====================");
//根据打印结果可以看出, v1和v2的 hash不一样说明是两个不同的对象,而输出的内容是一样的 我们发现拷贝成功了
video2.setName("克隆版本:java视频");
System.out.println("video2 = " + video2); //同时 克隆的对象也支持使用原对象方法
System.out.println("video1 = " + video1); //克隆体更新了 String型的name 而原型体没改变 说明基本数据类型是 深拷贝,不随克隆体改变而改变原型
date.setTime(20211111);
System.out.println("video2 = " + video2);
System.out.println("video1 = " + video1); //更改引用数据类型后,发现克隆体和原型体都改变了,说明 在这种方法下 引用类型是 浅拷贝
//克隆体在拷贝原型的引用数据类型时候 只是单纯的拷贝了引用类型的地址,他们依然使用(指向)同一个数据
//而深拷贝可以解决, 拷贝时不是单纯拷贝引用地址,连引用的数据对象一起拷贝,各指各的
}
}
深拷贝代码
Video类
import java.util.Date;
/**
* @author jdw
* @date 2021/8/19-14:18
*/
public class Video implements Cloneable {
private String name;
private Date createTime;
@Override
protected Object clone() throws CloneNotSupportedException { //实现深拷贝
Object obj = super.clone();
//将 Object对象(所有类的父类) 转化为 Video对象 // 父类 强转为 子类 --增肥
Video video = (Video) obj; //向下转型,此时的 video 和 obj 都是引用类型 都指向堆中同一片空间 相互的操作会同步
//将对象的属性也克隆
video.createTime = (Date) this.createTime.clone();//this代表当前类 当前类的引用数据类型也克隆一下
return video; //所以这里 返回 video 和 obj是 一样的
}
public Video() {
}
public Video(String name, Date createTime) {
this.name = name;
this.createTime = createTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", createTime=" + createTime +
'}';
}
}
Client类
import java.util.Date;
/**
* @author jdw
* @date 2021/8/19-14:58
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
//原型对象
Date date;
date = new Date();
Video video1 = new Video("java视频", date);
System.out.println("video1 = " + video1);
System.out.println("video1.hash = " + video1.hashCode());
// v1 克隆出 v2
// Video video2 = new Video("java视频", date); //以前:
Video video2 = (Video) video1.clone();
System.out.println("video2 = " + video2);
System.out.println("video2.hash = " + video2.hashCode());
System.out.println("====================");
//根据打印结果可以看出, v1和v2的 hash不一样说明是两个不同的对象,而输出的内容是一样的 我们发现拷贝成功了
video2.setName("克隆版本:java视频");
System.out.println("video2 = " + video2); //同时 克隆的对象也支持使用原对象方法
System.out.println("video1 = " + video1); //克隆体更新了 String型的name 而原型体没改变 说明基本数据类型是 深拷贝,不随克隆体改变而改变原型
date.setTime(20211111);
System.out.println("video2 = " + video2);
System.out.println("video1 = " + video1);
}
}
总结:
-
创建原型接口, 并在其中声明
克隆
方法。 如果你已有类层次结构, 则只需在其所有类中添加该方法即可。 -
原型类必须另行定义一个以该类对象为参数的构造函数。 构造函数必须复制参数对象中的所有成员变量值到新建实体中。 如果你需要修改子类, 则必须调用父类构造函数, 让父类复制其私有成员变量值。
如果编程语言不支持方法重载, 那么你可能需要定义一个特殊方法来复制对象数据。 在构造函数中进行此类处理比较方便, 因为它在调用
new
运算符后会马上返回结果对象。 -
克隆方法通常只有一行代码: 使用
new
运算符调用原型版本的构造函数。 注意, 每个类都必须显式重写克隆方法并使用自身类名调用new
运算符。 否则, 克隆方法可能会生成父类的对象。 -
你还可以创建一个中心化原型注册表, 用于存储常用原型。
你可以新建一个工厂类来实现注册表, 或者在原型基类中添加一个获取原型的静态方法。 该方法必须能够根据客户端代码设定的条件进行搜索。 搜索条件可以是简单的字符串, 或者是一组复杂的搜索参数。 找到合适的原型后, 注册表应对原型进行克隆, 并将复制生成的对象返回给客户端。
最后还要将对子类构造函数的直接调用替换为对原型注册表工厂方法的调用。