一、前言
原型模式是一种创建型设计模式,它允许在运行时通过克隆现有对象来创建新对象,而不是通过常规的构造函数创建。在原型模式中,一个原型对象可以克隆自身来创建新的对象,这个过程可以通过深度克隆或浅克隆来实现。简单说原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需知道任何创建的细节。
原型模式包含三个角色:
①Prototype(抽象原型类):定义用于克隆自身的接口,通常是一个抽象类或接口,其中声明了一个克隆方法 clone(),用于创建一个新的对象,具体的克隆操作由子类实现。
②ConcretePrototype(具体原型类):实现 Prototype 接口,实现 clone() 方法,完成对象的克隆操作。每个具体原型类都有自己的一些属性和方法,不同的具体原型类之间具有不同的实现方式。
③Client(客户类):使用原型模式创建新的对象,在原型模式中,客户端通过克隆接口来创建新的对象,而不是通过实例化的方式。客户端需要获取一个原型对象,并通过克隆方法来创建新的对象,从而避免了创建新对象的开销。
原型模式的克隆分为浅拷贝和深拷贝两种。
二、浅拷贝
创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
这里抽象原型类不用自己创建,都是Object,里面有clone()方法:
具体原型类实现Cloneable,里面重写clone方法,直接super调用父类的clone方法即可:
import java.util.List;
/**
* @Author dengyifan
* @create 2023/11/10 10:19
* @description
*/
public class UserPrototype implements Cloneable{
private String name;
private Integer age;
private List<String> messages;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<String> getMessages() {
return messages;
}
public void setMessages(List<String> messages) {
this.messages = messages;
}
@Override
protected UserPrototype clone() throws CloneNotSupportedException {
return (UserPrototype) super.clone();
}
}
客户类:
import java.util.ArrayList;
import java.util.List;
/**
* @Author dengyifan
* @create 2023/11/10 10:22
* @description
*/
public class PrototypeClient {
public static void main(String[] args) throws CloneNotSupportedException {
UserPrototype userPrototype1 = new UserPrototype();
userPrototype1.setName("tom");
userPrototype1.setAge(18);
List<String> messages1 = new ArrayList<>();
messages1.add("test");
userPrototype1.setMessages(messages1);
UserPrototype userPrototype2 = userPrototype1.clone();
System.err.println(String.format("两个对象是否一致:%s", userPrototype1 == userPrototype2));
System.err.println(String.format("两个对象的name属性是否一致:%s", userPrototype1.getName() == userPrototype2.getName()));
System.err.println(String.format("两个对象的messages属性是否一致:%s", userPrototype1.getMessages() == userPrototype2.getMessages()));
System.err.println(userPrototype1.getMessages());
System.err.println(userPrototype2.getMessages());
messages1.clear();
messages1.add("test1");
System.err.println(userPrototype1.getMessages());
System.err.println(userPrototype2.getMessages());
}
}
运行结果:
通过结果可以看到,通过克隆的对象与原型对象的引用不一致,说明再内存中存在两个不同的对象,一个是原型对象,一个是克隆生成的对象。而这里属性都显示是一致的,结果都是true,说明两个对象的成员对象是同一个,也就是对象本身是复制了,但是其成员的对象再内存中没有复制。并且我们修改原型的messages属性,克隆对象的messages也跟着改变,说明属性确实是同一个对象的引用。
三、深拷贝
深拷贝中除了原型对象与克隆生成的对象被复制以外,里面的属性也需要被复制,即地址都不一样。这里深拷贝实现了Serializable,即进行了序列化。
import java.io.*;
import java.util.List;
/**
* @Author dengyifan
* @create 2023/11/10 15:43
* @description
*/
public class UserPrototypeDeep implements Serializable {
private String name;
private Integer age;
private List<String> messages;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<String> getMessages() {
return messages;
}
public void setMessages(List<String> messages) {
this.messages = messages;
}
protected UserPrototypeDeep deepClone() throws Exception{
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bao);
oos.writeObject(this);
//将对象从流中取出
ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
return (UserPrototypeDeep) ois.readObject();
}
}
import java.util.ArrayList;
import java.util.List;
/**
* @Author dengyifan
* @create 2023/11/10 15:46
* @description
*/
public class PrototypeDeepClient {
public static void main(String[] args) throws Exception {
UserPrototypeDeep userPrototypeDeep1 = new UserPrototypeDeep();
userPrototypeDeep1.setAge(19);
userPrototypeDeep1.setName("tom");
List<String> messages1 = new ArrayList<>();
messages1.add("test");
userPrototypeDeep1.setMessages(messages1);
UserPrototypeDeep userPrototypeDeep2 = userPrototypeDeep1.deepClone();
System.err.println(String.format("两个对象是否一致:%s", userPrototypeDeep1 == userPrototypeDeep2));
System.err.println(String.format("两个对象的name属性是否一致:%s", userPrototypeDeep1.getName() == userPrototypeDeep2.getName()));
System.err.println(String.format("两个对象的messages属性是否一致:%s", userPrototypeDeep1.getMessages() == userPrototypeDeep2.getMessages()));
System.err.println(userPrototypeDeep1.getMessages());
System.err.println(userPrototypeDeep2.getMessages());
messages1.clear();
messages1.add("test1");
System.err.println(userPrototypeDeep1.getMessages());
System.err.println(userPrototypeDeep2.getMessages());
}
}
运行结果:
可以看出除了对象本身不一致以外,里面的属性也不一致,当修改原型对象里面引用对象的属性时,克隆对象的属性不会发生变化,即也证明属性不是同一个对象。
对于原型模式的应用场景,主要有以下几点:
1. 当对象的创建过程非常复杂或耗费大量资源时,可以使用原型模式来复制一个现有对象,从而避免重复创建对象。
2. 当需要创建多个相似的对象时,可以使用原型模式来减少重复代码,提高代码的重用性。
3. 当创建对象的过程需要大量的数据准备或配置时,可以使用原型模式来复制一个已经配置好的对象,从而避免重复的数据准备或配置过程。
4. 当需要动态生成对象的子类时,可以使用原型模式来复制一个已有的对象,并对其进行修改,从而避免重新编写子类的代码。
5. 当需要保护对象的状态时,可以使用原型模式来创建对象的副本,并将副本交给其他对象使用,从而避免原始对象的状态被修改。