# 原型模式
1.简介
原型模式是一种创建型设计模式,使你能够复制已有对象,而又无需代码依赖它们所属的类。
原型模式其实就是一种克隆对象的方法,在我们的编码时候是很常见的,比如我们常用的的BeanUtils.copyProperties就是一种对象的浅copy,其实现在我们实例化对象操作并不是特别耗费性能,所以在针对一些特殊场景我们还是需要克隆那些已经实例化的对象的:
- 依赖外部资源或硬件密集型操作,比如数据库查询,或者一些存在IO操作的场景
- 获取相同对象在相同状态的拷贝从而不需要重复创建获取状态的操作的情况
2.UML图
UML 类图也比较简单,只有两个部分:
- Prototype : 这个是一个接口,里面必须含有一个可以克隆自己的方法。在Java中,我们可以使用JDK自带的
java.lang.Cloneable
接口来替代此接口 - ConcretePrototype :实现了
Prototype
接口的原型对象,这个对象有个能力就是可以克隆自己。
3.示例
具体原型类:
java
package com.gs.designmodel.prototype.one;
/**
* @author: Gaos
* @Date: 2023-07-21 14:27
**/
public class Realizetype implements Cloneable {
public Realizetype() {
System.out.println("具体原型创建成功!");
}
@Override
public Realizetype clone() {
try {
System.out.println("具体原型复制成功");
return (Realizetype) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
测试类:
java
package com.gs.designmodel.prototype.one;
/**
* @author: Gaos
* @Date: 2023-07-21 14:30
**/
public class Test {
public static void main(String[] args) {
Realizetype realizetypeOne = new Realizetype();
Realizetype realizetypeClone = realizetypeOne.clone();
System.out.println(realizetypeOne == realizetypeClone);
}
}
结果:
arduino
具体原型创建成功!
具体原型复制成功
false
- 可以看到此时原对象与复制对象不是同一个对象。
clone
方法在执行的时候并不会去执行构造方法,因为此时对象是由内存以二进制流的方式进行拷贝,并不是通过构造方法。
此时我们需要理解深拷贝 和浅拷贝两种概念。
- **浅拷贝:**当拷贝类型只包含简单的数据类型,比如
int
、float
或者不可变的对象(字符串)时,就直接讲这些字段复制到新的对象中。而引用的对象没有复制而是将引用对象的地址复制一份给克隆对象。- **深拷贝:**不管拷贝对象里面简单数据类型还是引用对象类型都是会完全的复制一份到新的对象中。
也就是说浅拷贝 中,也就是我们使用的 Object
类中的 clone
方法只拷贝本对象,其内部的比如数组、引用对象等都不拷贝,还是指向原生对象的内部地址。这就意味着两个对象会共用一个私有变量,这非常的不安全。我们下面分别写两个例子来说明这种情况:
浅拷贝:
java
package com.gs.designmodel.prototype.two;
import java.util.ArrayList;
import java.util.List;
/**
* @author: Gaos
* @Date: 2023-07-21 15:07
*
* 浅拷贝
**/
public class ShallowCopy implements Cloneable{
private List<String> list = new ArrayList<>();
@Override
public ShallowCopy clone() {
try {
ShallowCopy clone = (ShallowCopy) super.clone();
// TODO:复制此处的可变状态,这样此克隆就不能更改初始克隆的内部项
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public void setValue(String value) {
this.list.add(value);
}
public List<String> getValue() {
return this.list;
}
}
测试类:
java
package com.gs.designmodel.prototype.two;
/**
* @author: Gaos
* @Date: 2023-07-21 15:11
**/
public class Test {
public static void main(String[] args) {
ShallowCopy shallowCopy = new ShallowCopy();
shallowCopy.setValue("张三");
// 此处克隆了一个对象
ShallowCopy clone = shallowCopy.clone();
clone.setValue("李四");
// 输出原始对象
System.out.println(shallowCopy.getValue());
// 输出克隆对象
System.out.println(clone.getValue());
}
}
结果:
css
[张三, 李四]
[张三, 李四]
我们发现原始对象和克隆对象中的 list
变量是共享的。
那么我们该如何去实现深拷贝呢?
-
第一种是在重写对象中的
clone
方法的时候针对里面的引用变量再进行一次拷贝。(如果是List对象,只能是先拿到浅拷贝,再通过浅拷贝的List对象进行遍历再调用引用对象的clone方法来实现深拷贝) 如果引用对象存在多级的情况下我们还要考虑用递归去实现,代码逻辑相对复杂。java@Override public DeepCopy clone() { try { DeepCopy clone = (DeepCopy) super.clone(); // 数组及引用类型需要单独进行clone,假如List<Student>类型 那对于List中所有引用对象都需要进行循环clone clone.list = (ArrayList<String>)this.list.clone(); return clone; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } }
-
第二种通过序列化把对象写入流中再从流中取出来。
java@Override public DeepCopy clone() { try { ByteArrayOutputStream bao = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bao); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (DeepCopy) ois.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException(e); } }
序列化的方式有很多,除了像上面示例这种较原生的之外,还可以通过引用一些第三方的jar包实现,如
Apache Commons Lang
序列化、Gson
序列化、Jackson
序列化等。另外需要注意序列化的javabean
需要实现Serializable
序列化接口,来标识一个对象可否被序列化,必要的时候(对象会产生一些变化的时候)还需要加上serialVersionUID
序列号。
接下来看一下深拷贝的完整示例:
java
package com.gs.designmodel.prototype.three;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author: Gaos
* @Date: 2023-07-21 15:19
*
* 深拷贝
**/
public class DeepCopy implements Cloneable, Serializable {
private ArrayList<String> list = new ArrayList<>();
// @Override
// public DeepCopy clone() {
// try {
// ByteArrayOutputStream bao = new ByteArrayOutputStream();
// ObjectOutputStream oos = new ObjectOutputStream(bao);
// oos.writeObject(this);
//
// ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
// ObjectInputStream ois = new ObjectInputStream(bis);
// return (DeepCopy) ois.readObject();
// } catch (IOException | ClassNotFoundException e) {
// throw new RuntimeException(e);
// }
// }
@Override
public DeepCopy clone() {
try {
DeepCopy clone = (DeepCopy) super.clone();
// 数组及引用类型需要单独进行clone,假如List<Student>类型 那对于List中所有引用对象都需要进行循环clone
clone.list = (ArrayList<String>)this.list.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
public void setValue(String value) {
this.list.add(value);
}
public List<String> getValue() {
return this.list;
}
}
测试类:
java
package com.gs.designmodel.prototype.three;
/**
* @author: Gaos
* @Date: 2023-07-21 15:30
**/
public class Test {
public static void main(String[] args) {
DeepCopy deepCopy = new DeepCopy();
deepCopy.setValue("张三");
DeepCopy clone = deepCopy.clone();
clone.setValue("李四");
System.out.println("原始对象:" + deepCopy.getValue());
System.out.println("复制对象:" + clone.getValue());
}
}
结果:
css
原始对象:[张三]
复制对象:[张三, 李四]
可以看到结果中两个对象已经互不影响了。
4.总结
原型模式是在内存二进制流的拷贝,要比new一个对象性能好很多,特别是在一个循环体类产生大量对象的时候更加明显。
什么时候使用原型模式呢:
1.对象创建过于复杂 :对象创建的时候需要经过计算、排序等操作。 2.对象时间过长 :需要查询数据库或者通过rpc调用。 3.对象过多:比如一个集合中有一百万个对象,我现在需要修改这一百万的对象的数据,但是原数据不能动,所以这个时候使用原型模式比较适合。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
实际项目中原型模式很少单独出现,一般和工厂模式一起出现,通过clone方法创建一个对象,然后由工厂方法提供给调用者。