携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情
简介
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
通俗地讲就是现在已有一个对象,但是现在又要创建另外一个一摸一样的对象时,我会使用clone方法来快速地将已有的对象复制一份。需要注意的是通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,通常对克隆所产生的对象进行修改对原型对象不会造成任何影响,每一个克隆对象都是相互独立的。
原型模式适用于以下几种场景下使用:
- 需要生成一大批很相像的类的对象时,不用每次去做重复的赋值工作
- 一个对象多个修改者的场景
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗
原型模式在理解上是非常简单的。就是通过复制来创建对象,但是要彻底弄清楚复制(clone)里的玄机和细节还是需要花点功夫的,下面我们通过一个例子来简单分析下。
案例
我们通过一个简单的例子,即Person类的复制来说明原型模式。
首先我们定义一个Person类,属性有姓名、出生日期(注意这2个属性的区别)
public class Person implements Cloneable {
public Person(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
private String name;
private Date birthday;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + ''' +
", birthday=" + birthday +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
这个Person的定义我觉得大家应该都没什么疑问,除了最后一个重新的clone方法大家可能见的不多,这个方法就是用来复制自己的,它是Object类就有的对象。在Object里它定义为一个native方法,即和底层打交道的方法,我们这里不过多细究,先用起来。
然后是一个应用类:
public class App {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date();
Person zhangsan = new Person("张三", date);
Person zhangsan1 = (Person)zhangsan.clone();
System.out.println("1:"+zhangsan);
System.out.println("2:"+zhangsan1);
System.out.println("3:"+(zhangsan == zhangsan1));
System.out.println("4:"+(zhangsan.getBirthday() == zhangsan1.getBirthday()));
zhangsan.setName("李四");
date.setTime(2345678);
System.out.println("5:"+zhangsan);
System.out.println("6:"+zhangsan1);
System.out.println("7:"+(zhangsan == zhangsan1));
System.out.println("8:"+(zhangsan.getBirthday() == zhangsan1.getBirthday()));
}
}
输出情况如下:
我们通过结果可以清晰地看到3和4位置输出的结果分别是false和true,说明复制的对象和原对象的分别指向了不同的内存区域,但是birthday属性指向的却是相同的一块区域。说明什么,说明我们拷贝只是拷贝了外层的对象和对象内的值类型的数据,而引用型的数据是不会被拷贝的。
这种拷贝叫做浅拷贝
那很显然我们真实场景中大部分是不想使用浅拷贝的,我们要全部拷贝一份,成为一个独立的对象,那我们就需要深拷贝了。
我们将clone方法修改下,如下:
@Override
public Object clone() throws CloneNotSupportedException {
Person p = (Person)super.clone();
Date d = (Date)this.birthday.clone();
p.setBirthday(d);
return p;
}
同样运行App应用类,结果如下:
如上,我们如愿以偿,实现了深拷贝。
总结
从上面的实践我们可以看到,原型模式的确在复杂对象的创建方面提供了很大的方便,但是其缺点也是比较明明显,因为存在浅拷贝和深拷贝的区别在里面,在使用时候很容易忽略这点,而造成bug。而且我们大多数情况下要用的深拷贝在引用对象多的情况下,实现深拷贝代码量也是不小的,这点使用的时候要权衡。