原型模式的核心在于拷贝源对象,UML类图如下:
其中主要有三个角色:
public interface Prototype {
Prototype 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 关键字来创建对象而是使用拷贝对象调用内部克隆方法创建的类的模式就是原型模式
但是大部分的克隆并不是像上文中的实现方式一样,一般都是直接基于内存二进制流进行拷贝,无需在经历夯实的对象初始化过程(不调用构造函数),这样性能提升很多,当对象构建过程比较耗时是,可以利用当前系统中已存在的对象作为原型,对其进行克隆
public class ConcretePrototypeB implements Cloneable{
private String name;
private Integer age;
private List 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 getFamily() {
return family;
}
public void setFamily(List 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 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;
}



优点:
缺点:
适用场景:
我们平常使用的BeanUtils就是原型模式