定义
Specify the kind of objects to create using a prototypical instance, and create new objects by copying this prototype. (使用原型实例指定将要创建的对象类型,通过复制这个实例创建新的对象。)
从定义中我们我们可以发现,该模式的前提是首先需要有一个对象,然后基于已有的对象复制生成新的对象,新生成的对象状态和原对象是一样的,不一样的是内存地址。
实现
public interface ICloneable<T> extends Cloneable {
public T copy();
}
public class Message implements ICloneable<Message>{
int what;
String descriotion;
@Override
public Message copy() {
Message clone = null;
try {
clone = (Message) clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
上述只有两个类,一个是ICloneable接口,该接口继承自java.lang.Cloneable接口,Cloneable接口是一个标记接口,本身没有什么方法,但是调用某个对象的clone方法必须要实现该接口,这里我借助该接口并扩展了ICloneable接口,所有ICloneable接口的实现类都必须要具备复制自己的能力,你可以发现,当前的设计中没有要求实现者必须是深拷贝或者浅拷贝。此外这里的接口作为一个标记来处理的方式很精巧,在日常开发中我们也可以这样做,比如设计一个防混淆的接口,所有该接口的实现者都在proguard中配置不混淆。
第二个类是ICloneable实现者,其copy方法对外提供了复制自身的能力,Message类中的what变量属于基本数据类型,该变量会被直接复制后赋值给新对象,而description变量是对象类型,目前copy方法采用了默认处理(clone方法默认是浅拷贝),所以该变量仍然指向旧对象中decscription变量所指向的string对象。
扩展
在解释原型模式的定义时,我们说原型对象是基于已有对象进行复制来生成新的对象,那么新生成对象时会执行构造函数吗?
在Java中,创建一个堆上对象有五种方式:
- 使用new关键字 → 调用了构造函数
- 使用Class类的newInstance方法 → 调用了构造函数
- 使用Constructor类的newInstance方法 → 调用了构造函数
- 使用clone方法 → 没有调用构造函数
- 使用反序列化 → 没有调用构造函数
clone的行为是很简单的。以堆上的内存存储解释的话(不计内务内存),对一个对象a的clone就是在堆上分配一个和a在堆上所占存储空间一样大的一块地方,然后把a的堆上内存的内容复制到这个新分配的内存空间上。
浅拷贝(Shallow Clone):当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没没复制。如我想将A复制一份出来,命名为为B,那么我在浅拷贝后,确实可以得到A和B。而且A和B的值也相等,但是,我将B的值稍作修改,A的值也会变动,这往往不是我们想要的。因为我们想拷贝一个副本出来,二者也能独立,这样才算拷贝。但是,浅拷贝后A和B却是指向同一片地址空间,也就是二者共用一个值,改一个,两个都变。
浅拷贝实现:
重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,需要拷贝的类需要将clone方法的作用域修改为public类型。
注:Object类的clone方法只会拷贝java中的8中基本类型以及他们的封装类型,另外还有String类型。对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。
public class Student implements Cloneable {
private String name;
private int age;
private Address addr;
public Student(String name, int age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", addr=" + addr +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Address implements Cloneable {
private String addrName;
public Address(String addrName) {
this.addrName = addrName;
}
public String getAddrName() {
return addrName;
}
public void setAddrName(String addrName) {
this.addrName = addrName;
}
@Override
public String toString() {
return "Address{" +
"addrName='" + addrName + '\'' +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student("zhangsan", 13, new Address("China"));
Student clone = (Student) student.clone();
System.out.println(student);
System.out.println(clone);
student.setName("lisi");
student.getAddr().setAddrName("Canada");
System.out.println(student);
System.out.println(clone);
}
}
深拷贝(Deep Clone):除了对象本身被复制外,对象所包含的所有成员变量也被复制,就是我们想要的那种拷贝,即有一个副本,与原者老死不想往来,互不影响。
把上面的Student中clone方法改造一下,即可以实现深拷贝:
@Override
public Object clone() throws CloneNotSupportedException {
Student newStu = (Student) super.clone();
newStu.addr = (Address) this.getAddr().clone();
return newStu;
}
拷贝的时候,将对象中的引用数据也拷贝一份即可。
2、可以使用序列化 + 反序列化的方式实现深拷贝:
public Object deepCopy(Object object) {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(object);
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return oi.readObject();
}
原型模式的优点:
- 简化对象的创建过程,通过复制一个已有对象实例可以提高新实例的创建效率
- 扩展性好
- 提供了简化的创建结构,原型模式中的产品的复制是通过封装在原型类中的克隆方法实现的,无需专门的工厂类来创建产品
- 可以通过深克隆的方式保存对象的状态,使用原型模式将对象复制一份并其状态保存起来,以便在需要的时候使用,可辅助实现撤销操作
- 原型模式的缺点:
- 需要为每一个类准备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有类进行改造时,需要修改原代码,违背了开闭原则
- 在实现深克隆时需要写较复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类必须支持深克隆,实现起来较烦较烦....
- 原型模式的适用环境:
- 创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量修改
- 系统要保存对象的状态,而对象的状态变化很小
- 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更方便