原型模式的核心在于拷贝源对象,UML类图如下:
其中主要有三个角色:
- 客户(client):客户类提出创建对象的请求
- 抽象原型(Prototype):规定拷贝接口
- 具体原型(ConcreatePrototyoe):被拷贝对象
原型模式通用写法
public interface Prototype<T> {
Prototype<T> clone();
}
复制代码
public class ConcretePrototypeB implements Prototype{
private String name;
private Integer age;
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;
}
@Override
public ConcretePrototypeB clone() {
ConcretePrototypeB concretePrototypeB = new ConcretePrototypeB();
concretePrototypeB.setName(this.name);
concretePrototypeB.setAge(this.age);
return concretePrototypeB;
}
@Override
public String toString() {
return "ConcretePrototypeB{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
ConcretePrototypeB concretePrototypeB = new ConcretePrototypeB();
concretePrototypeB.setName("张三");
concretePrototypeB.setAge(19);
ConcretePrototypeB concretePrototypeB_copy = concretePrototypeB.clone();
System.out.println(concretePrototypeB);
System.out.println(concretePrototypeB_copy);
}
}
上述的例子就是最简单的原型模式,可能有些人会觉得在里面set值和在外面好像一样只不过将对象创建设置的方法移到里面clone
方法里面了而已,话虽然是这么说,的确看到的也是这样,但是有些对象的变量是私有的外部是不能访问的,此时再外部进行实例化然后在进行复制那么必然一些私有的变量是没有办法复制的。所以还是有一点点差别的。
通过这个例子再说一下原型模式,用通俗点话来讲其实就是在你不是通过在外部使用new
关键字来创建对象而是使用拷贝对象调用内部克隆方法创建的类的模式就是原型模式
但是大部分的克隆并不是像上文中的实现方式一样,一般都是直接基于内存二进制流进行拷贝,无需在经历夯实的对象初始化过程(不调用构造函数),这样性能提升很多,当对象构建过程比较耗时是,可以利用当前系统中已存在的对象作为原型,对其进行克隆
原型模式实现改造(实现Cloneable接口)
public class ConcretePrototypeB implements Cloneable{
private String name;
private Integer age;
private List<String> family;
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> getFamily() {
return family;
}
public void setFamily(List<String> family) {
this.family = family;
}
@Override
public ConcretePrototypeB clone() {
ConcretePrototypeB clone = null;
try {
clone = (ConcretePrototypeB) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
return clone;
}
@Override
public String toString() {
return "ConcretePrototypeB{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
复制代码
只需要实现Cloneable
接口实现以下clone
方法即可这样对于很复杂的对象赋值其实就很简单了
不过这里也有个小问题如下:
public class Test {
public static void main(String[] args) {
ConcretePrototypeB concretePrototypeB = new ConcretePrototypeB();
concretePrototypeB.setName("张三");
concretePrototypeB.setAge(19);
List<String> family = new ArrayList<>();
family.add("爷爷");
family.add("奶奶");
family.add("爸爸");
family.add("妈妈");
family.add("妹妹");
concretePrototypeB.setFamily(family);
ConcretePrototypeB concretePrototypeB_copy = concretePrototypeB.clone();
concretePrototypeB_copy.setName("李四");
concretePrototypeB_copy.getFamily().add("外公");
concretePrototypeB_copy.getFamily().add("外婆");
System.out.println("张三" + concretePrototypeB);
System.out.println("李四" + concretePrototypeB_copy);
}
}
复制代码
上图中的打印结果可以知道Cloneable
接口实现的克隆是浅拷贝,所以我们在该其他对象的时候连带所有的对象都改了
浅拷贝:赋值对象的引用地址,如下图所示:
两个指针指向了同一片地址,所以一个修改所有的都修改了
使用序列化实现深度克隆
基于上述的问题我们可以再改造一下添加deepClone
方法如下代码
public ConcretePrototypeB deepClone() {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(this);
ByteArrayInputStream inputStream = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(inputStream);
ConcretePrototypeB deepClone = (ConcretePrototypeB)ois.readObject();
out.close();
oos.close();
inputStream.close();
ois.close();
return deepClone;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
原型模式在源码中的应用
原型模式的优缺点以及应用场景
优点:
- 性能优良,Java自带的原型模式是基于内存二进制流的拷贝,比直接new一个对象性能上提升了很多
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用
缺点:
- 需要为每个类配置克隆方法
- 克隆方法位于类的内部,当对已有类进行改造的时候,需要修改克隆代码,违背开闭原则
- 实现深度克隆的时候代码复杂如果设计嵌套类更麻烦
适用场景:
- 类初始化消耗资源较多的情况
- new的过程很复杂
- 构造函数很复杂
- 需要循环产生大量对象的时候
我们平常使用的BeanUtils就是原型模式