原型模式
原型模式(Prototype) 的定义如下:
- 用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。
- 在这里,原型实例指定了要创建的对象的种类。
- 用这种方式创建对象非常高效,根本无须知道对象创建的细节。
原型模式的优点:
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
原型模式的缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
uml 类图
- Prototype:原型类,声明一个克隆自己的接口
- ConcretePrototype:具体的原型类,实现一个克隆自己的操作
- Client:让一个原型对象克隆自己,从而创建一个新的对象(属性一样)
代码演示:
- Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。
class Realizetype implements Cloneable {
Realizetype() {
System.out.println("具体原型创建成功!");
}
public Object clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return super.clone();
}
}
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Realizetype obj1 = new Realizetype();
Realizetype obj2 = (Realizetype) obj1.clone();
System.out.println("obj1==obj2? " + (obj1 == obj2));
}
}
运行结果:
具体原型创建成功!
具体原型复制成功!
obj1==obj2? false
Process finished with exit code 0
浅克隆
在 Java 提供的 API 中,不需要手动创建抽象原型接口。Java 中的 Object 类提供了浅克隆的 clone() 方法。因为 Java 已经内置了 Cloneable 抽象原型接口,自定义的类型只需实现该接口并重写 Object.clone() 方法就可实现对象的浅克隆。
一般情况下,使用 clone() 方法需要满足以下条件:
- 对任何对象 o,都有 o.clone() != o。换言之,克隆对象与原型对象不是同一个对象。
- 对任何对象 o,都有 o.clone().getClass() == o.getClass()。换言之,克隆对象与原型对象的类型一样。
- 如果对象 o 的 equals() 方法定义恰当,则 o.clone().equals(o) 应当成立。
我们在设计自定义类的 clone() 方法时,应当遵守这 3 个条件。一般来说,这 3 个条件中的前 2 个是必需的,第 3 个是可选的。
代码实现浅克隆:
public class Client {
public static void main(String[] args) {
ConcretePrototype type = new ConcretePrototype("original");
System.out.println(type);
ConcretePrototype cloneType = null;
try {
cloneType = type.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
cloneType.desc = "clone";
System.out.println(cloneType);
}
static class ConcretePrototype implements Cloneable {
private String desc;
public ConcretePrototype(String desc) {
this.desc = desc;
}
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException {
ConcretePrototype cloneType = null;
try {
cloneType = (ConcretePrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return cloneType;
}
@Override
public String toString() {
return "ConcretePrototype{" + "desc='" + desc + '\'' + '}';
}
}
}
运行结果:
ConcretePrototype{desc='original'}
ConcretePrototype{desc='clone'}
Process finished with exit code 0
super.clone() 方法直接从堆内存中以二进制流的方式进行复制,重新分配一个内存块,因此其效率很高。由于 super.clone() 方法基于内存复制,因此不会调用对象的构造函数,也就是不需要经历初始化过程。
但是 super.clone() 也不能满足所有需求,如果类中存在引用类型对象,则原型对象和克隆对象的该属性会指向同一个对象的引用。如下代码演示:
import java.util.List;
public class ConcretePrototype implements Cloneable{
private int age;
private String name;
private List<String> hobbies;
@Override
protected Object clone(){
try {
return (ConcretePrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
", hobbies=" + hobbies +
'}';
}
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;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
}
客户端测试
import java.util.ArrayList;
import java.util.List;
public class Client1 {
public static void main(String[] args) {
ConcretePrototype type = new ConcretePrototype();
type.setAge(18);
type.setName("耀");
List<String> hobbies = new ArrayList<>();
hobbies.add("对线孙策");
hobbies.add("对线铠");
type.setHobbies(hobbies);
ConcretePrototype cloneType = (ConcretePrototype) type.clone();
cloneType.getHobbies().add("对线吕布");
System.out.println("原型对象:" + type);
System.out.println("克隆对象:" + cloneType);
}
}
运行结果:
原型对象:ConcretePrototype{age=18, name='耀', hobbies=[对线孙策, 对线铠, 对线吕布]}
克隆对象:ConcretePrototype{age=18, name='耀', hobbies=[对线孙策, 对线铠, 对线吕布]}
Process finished with exit code 0
我们给克隆对象新增一个属性 hobbies 之后,发现原型对象也随之变化,这显然不符合预期。因为我们希望克隆对象和原型对象是俩个独立的对象,不再有联系。
从测试结果来看,应该是 hobbies 共用了一个内存地址,意味着复制的不是值,而是引用的地址。这样的话,如果我们修改任意一个对象中的属性值,protoType 和 cloneType 的 hobbies 值都会改变。这就是我们常说的浅克隆,只是完整复制了值类型数据,没有复制引用对象。
换言之,所有的引用对象仍然指向原来的对象,显然不是我们想要的结果。那如何解决这个问题呢?
深克隆(使用序列化)
在上述基础上继续改造,增加一个 deepClone() 方法。
代码演示:
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class ConcretePrototype implements Cloneable, Serializable {
private int age;
private String name;
private List<String> hobbies;
@Override
protected Object clone(){
try {
return (ConcretePrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public ConcretePrototype deepCloneHobbies() {
try {
ConcretePrototype result = (ConcretePrototype) super.clone();
result.hobbies = (List)((ArrayList)result.hobbies).clone();
return result;
} 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;
}
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
", hobbies=" + hobbies +
'}';
}
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;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
}
客户端测试
import java.util.ArrayList;
import java.util.List;
public class Client2 {
public static void main(String[] args) {
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("耀");
List<String> hobbies = new ArrayList<>();
hobbies.add("对线孙策");
hobbies.add("对象铠");
prototype.setHobbies(hobbies);
ConcretePrototype cloneType = prototype.deepCloneHobbies();
cloneType.getHobbies().add("对线吕布");
System.out.println("原型对象:" + prototype);
System.out.println("克隆对象:" + cloneType);
System.out.println(prototype == cloneType);
System.out.println("原型对象的爱好:" + prototype.getHobbies());
System.out.println("克隆对象的爱好:" + prototype.getHobbies());
System.out.println(prototype.getHobbies() == cloneType.getHobbies());
}
}
运行结果:
原型对象:ConcretePrototype{age=18, name='耀', hobbies=[对线孙策, 对象铠]}
克隆对象:ConcretePrototype{age=18, name='耀', hobbies=[对线孙策, 对象铠, 对线吕布]}
false
原型对象的爱好:[对线孙策, 对象铠]
克隆对象的爱好:[对线孙策, 对象铠]
false
Process finished with exit code 0
结果表明,实现了深克隆。
|