设计模式之原型模式详解
一、什么原型模式
原型模式 (
Prototype Pattern
) 是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,属于创建型模式。原型模式的核心在于拷贝原型对象。简单来说,对于不通过new 关键字,而是通过对象拷贝来实现创建对象的模式就可以称为原型模式。
二、原型模式的角色
我们先来看下原型模式的通用类图:
- 客户 (
Client
):客户类提出创建对象的请求 - 抽象原型 (
Prototype
) : 规定拷贝的接口信息 - 具体原型 (
Concrete Prototype
):被拷贝的对象
二、原型模式示例
先创建一个原型IPrototype
接口:
public interface IPrototype<T> {
T clone();
}
创建具体需要克隆的对象 ConcreteProtoType
:
@Data
public class ConcretePrototype implements IPrototype<ConcretePrototype> {
private int age;
private String name;
@Override
public ConcretePrototype clone() {
ConcretePrototype concretePrototype = new ConcretePrototype();
concretePrototype.setAge(this.age);
concretePrototype.setName(this.name);
return concretePrototype;
}
}
测试:
public class PrototypeTest {
public static void main(String[] args) {
//创建原型对象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(16);
prototype.setName("YanZi");
System.err.println(prototype);
//拷贝原型对象
ConcretePrototype cloneObj = prototype.clone();
System.err.println(cloneObj);
}
}
控制台输出:
这样一个最简单的原型模式就完成了,但是上面的复制过程是我们自己完成的,如果属性非常多,那么写起来会很繁琐。在JDK中已经提供了一个现成的API,只需要实现CloneAble接口即可。我们接着改造一下代码:
@Data
public class ConcretePrototype implements Cloneable{
private int age;
private String name;
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
以上代码用上面同样的测试类,输出结果是一样的。紧接着我们再给ConcretePrototype
加上一个hobbies
属性来说明深、浅拷贝的问题:
@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 ShallowCopyTest {
public static void main(String[] args) {
//创建原型对象
ConcretePrototype prototypeObj = new ConcretePrototype();
prototypeObj.setAge(16);
prototypeObj.setName("YanZi");
List<String> hobbies = new ArrayList<String>();
hobbies.add("Java");
hobbies.add("Python");
prototypeObj.setHobbies(hobbies);
//拷贝原型对象
ConcretePrototype cloneObj = prototypeObj.clone();
cloneObj.getHobbies().add("Go");
System.err.println("原型对象:" + prototypeObj);
System.err.println("克隆后的对象:" + cloneObj);
System.err.println(prototypeObj == cloneObj);
System.err.println("原型对象的爱好:" + prototypeObj.getHobbies());
System.err.println("克隆对象的爱好:" + cloneObj.getHobbies());
System.err.println(prototypeObj.getHobbies() == cloneObj.getHobbies());
}
}
控制台输出:
从输出结果可以看出,在我们原型对象和克隆后的对象中hobbies
属性值在克隆前后是一样的,打印出hobbies
属性在内存中的地址值也是一样的,这说明了克隆过程中只是完整的复制了值类型的数据,没有赋值引用对象,也就是所有的引用对象仍然指向原来的对象。只要我们改变克隆前后任一对象中的hobbies
属性,两个对象中的hobbies
值都会改变!接下来我们使用深度克隆来解决这个问题:
@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;
}
}
}
我们再用同样的测试类来测试一下,只是改变了克隆方式:
public class DeepCloneTest {
public static void main(String[] args) {
//创建原型对象
ConcretePrototype prototypeObj = new ConcretePrototype();
prototypeObj.setAge(16);
prototypeObj.setName("YanZi");
List<String> hobbies = new ArrayList<String>();
hobbies.add("Java");
hobbies.add("Python");
prototypeObj.setHobbies(hobbies);
//拷贝原型对象
ConcretePrototype cloneObj = prototypeObj.deepClone();
cloneObj.getHobbies().add("Go");
System.err.println("原型对象:" + prototypeObj);
System.err.println("克隆后的对象:" + cloneObj);
System.err.println(prototypeObj == cloneObj);
System.err.println("原型对象的爱好:" + prototypeObj.getHobbies());
System.err.println("克隆对象的爱好:" + cloneObj.getHobbies());
System.err.println(prototypeObj.getHobbies() == cloneObj.getHobbies());
}
}
从控制台输出可以看出已经达到了我们的预期:
-
优点
- 性能较好,Java自带的原型模式是基于内存二进制流的拷贝,比直接new一个对象性能上提升了许多
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建对象的过程
-
缺点
- 需要为每一个类配置一个克隆方法
- 克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违反了开闭原则
三、注意
当我们克隆的目标对象是单例对象的时候,我们使用深克隆可能会破坏掉单例,深克隆要视情况而使用