原型模式是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,属于创建型模式。
原型模式的核心在于拷贝原型对象。以系统中已存在的一个对象为原型,直接基于内存二进制流进行拷贝,无需再经历耗时的对象初始化过程(不调用构造函数),性能提升许多。当对象的构建过程比较耗时时,可以利用系统中已存在的对象作为原型,对其进行克隆(一般是基于二进制流的复制),躲避初始化过程,使得新对象的创建时间大大减少。我们先来看一个简单的案例结构图。
从UML图中,我们可以看出主要包含三个角色:
客户端
客户端提出创建的请求
抽象原型
规定拷贝接口
具体原型
被拷贝的对象
应用场景
按照如上类图,一个标准的原型模式代码应该具有三部分。首先我们创建原型IPrototype
接口:
public interface IPrototype<T> {
T clone();
}
创建具体的需要克隆的对象ConcretePrototype
:
public class ConcretePrototype implements IPrototype{
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public ConcretePrototype clone() {
ConcretePrototype current = new ConcretePrototype();
current.setAge(this.age);
current.setName(this.name);
return current;
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
测试代码:
public class Main {
public static void main(String[] args) {
// 创建原型对象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setName("CC");
prototype.setAge(18);
System.out.println(prototype);
// 拷贝
ConcretePrototype clone = prototype.clone();
System.out.println(clone);
}
}
以上便是一个最简单的原型模式。虽然在这个简单场景下,我们这样操作好像复杂了,但是如果有几百个属性需要复制,采用这个方法就比较方便了。但是,上面的复制过程是我们自己完成的,在实际编码中我们一般不会做这种体力劳动,一般我们会采用浅克隆,深克隆两种方法。
普通写法中,我们是自己定义的一个克隆接口,实际上JDK已经帮我们实现了一个现成的API,我们只需要实现Cloneable
接口即可。
我们来改造下ConcretePrototype
类的代码:
public class ConcretePrototype implements Cloneable {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
这个时候重新测试,可以得到同样的结果。通过这个方式再多的属性赋值我们也能轻而易举的搞定了。
但是这种方法也会有点问题,我们再来测试一下,首先我们在原型类中添加要给爱好属性:
@Data
public class ConcretePrototype implements Cloneable {
private int age;
private String name;
private List<String> hobbies;
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
修改客户端测试代码:
public class Main {
public static void main(String[] args) {
// 创建原型对象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setName("CC");
prototype.setAge(18);
List<String> hobbies = new ArrayList<>();
hobbies.add("书法");
hobbies.add("音乐");
prototype.setHobbies(hobbies);
System.out.println(prototype);
// 拷贝
ConcretePrototype clone = prototype.clone();
clone.getHobbies().add("游戏");
System.out.println(clone); //克隆对象
System.out.println(prototype); //原型对象
}
}
ConcretePrototype(age=18, name=CC, hobbies=[书法, 音乐])
ConcretePrototype(age=18, name=CC, hobbies=[书法, 音乐, 游戏])
ConcretePrototype(age=18, name=CC, hobbies=[书法, 音乐, 游戏])
我们给克隆对象添加了一个爱好后,发现原型对象也发生了变化,这显然不符合我们的预期。因为我们希望克隆出来的对象应该和原型对象是两个独立的对象,不应该再有联系了。从测试结果分析来看,应该是hobbies共用了一个内存地址,意味着复制的不是值,而是引用的地址。这样的话如果我们修改任意一个对象的属性值,两个对象的值都会变化。这就是我们常说的浅克隆:只是完整复制了值类型数据,没有复制引用对象。这显然不是我们想要的结果,为了处理这个问题,我们需要使用到深克隆。
深克隆我们这里主要使用序列化以及反序列化来实现,当然也可以使用其他方法,比如json的方式,通过json字符串将其转换为对象实习。
我们继续改造代码,增加一个deepClone
方法。
由于使用序列化,所以我们需要实现Serializable
接口。
@Data
public class ConcretePrototype implements Cloneable,Serializable {
private int age;
private String name;
private List<String> hobbies;
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public ConcretePrototype deepClone() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (ConcretePrototype) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
这个时候我们再去测试,发现结果是我们想要的了,两个对象互不影响。
如果我们克隆的对象是单例对象,那意味着克隆就会破坏单例。实际上防止克隆破坏单例模式的解决思路非常简单,禁止克隆即可。要么我们直接单例类不实现Cloneable
接口;要么我们重写clone方法,在clone方法中直接返回单例对象。
@Data
public class ConcretePrototype implements Cloneable {
private int age;
private String name;
private List<String> hobbies;
private static ConcretePrototype instance = new ConcretePrototype();
private static ConcretePrototype getInstance(){
return instance;
}
@Override
public ConcretePrototype clone() {
return instance;
}
}
优点
缺点