1 概述
原型模式:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。原型模式是为了解决一些不必要的对象创建过程。 原型表明了该模式应该有一个样板实例,用户从这个样板对象中复制出一个内部属性一致性的对象,这个过程就是所谓的“克隆”。原型模式多用于创建复杂的或者构造耗时的实例。
2 使用场景
- 类初始化需要消耗非常多的资源,如数据、硬件资源等
- 通过个过new产生一个对象需要非常繁琐的书库准备或访问权限
- 一个对象需要提供给其他对象访问时,为了防止该对象被其他对象修改,可以使用原型模式拷贝对象供其他对象调用,即保护性拷贝
3 UML类图
角色介绍:
- Client:客户端用户
- Prototype:抽象类或接口,声明具备clone能力
- ConcretePrototype:具体的原型类
4 原型模式实现
public class Person implements Cloneable {
private String name;
private int age;
private ArrayList<String> childs = new ArrayList<>();
public Person() {
System.out.println("Person的构造函数");
}
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 ArrayList<String> getChilds() {
return childs;
}
public void setChilds(String child) {
this.childs.add(child);
}
@Override
protected Person clone() {
try {
Person person = (Person) super.clone();
person.name = this.name;
person.age = this.age;
person.childs = this.childs;
return person;
} catch (CloneNotSupportedException e) {
}
return null;
}
@Override
public String toString() {
return "Person[name=" + name + ", age=" + age + ", childs=" + childs + "]";
}
}
public class Client {
public static void main(String[] args) {
Person person = new Person();
person.setName("张三");
person.setAge(28);
person.setChilds("张一");
person.setChilds("张二");
System.out.println(person.toString());
Person person2 = person.clone();
System.out.println(person2.toString());
person2.setName("李四");
person2.setAge(30);
person2.setChilds("李一");
person2.setChilds("李二");
System.out.println(person2.toString());
System.out.println(person.toString());
}
}
输出结果如下:
Person的构造函数
Person[name=张三, age=28, childs=[张一, 张二]]
Person[name=张三, age=28, childs=[张一, 张二]]
Person[name=李四, age=30, childs=[张一, 张二, 李一, 李二]]
Person[name=张三, age=28, childs=[张一, 张二, 李一, 李二]]
可以看到,person2是通过person.clone()创建的,并且person2第一次输出的时候和person一样,即person2是person的一份拷贝,他们的内容是一样的。但person2修改内容后,person的name和age还是和原来一样,没有被person2的修改而改变,但是,person2修改了childs的值,childs是一个ArrayList
5 浅拷贝与深拷贝
上面的原型模式的实现实际上只是一个浅拷贝,即影子拷贝,只是将原始Person,即张三的所有字段引用拷贝了一份而已,由于childs是一个对象,因此person和person2的childs都指向同一个对象,哪一个Person修改了childs值,都能影响另外一个Person的childs值。解决这个问题需要采用深拷贝。 深拷贝在拷贝对象时,对于引用型的字段也采用拷贝的形式,而不是单纯引用的形式。
上面例子的clone方法修改如下:
@Override
protected Person clone() {
try {
Person person = (Person) super.clone();
person.name = this.name;
person.age = this.age;
person.childs = (ArrayList<String>) this.childs.clone();
return person;
} catch (CloneNotSupportedException e) {
}
return null;
}
打印结果如下:
Person的构造函数
Person[name=张三, age=28, childs=[张一, 张二]]
Person[name=张三, age=28, childs=[张一, 张二]]
Person[name=李四, age=30, childs=[张一, 张二, 李一, 李二]]
Person[name=张三, age=28, childs=[张一, 张二]]
可以看到person2的修改也影响不了person的childs值了。
6 Android源码中的原型模式实现
在Android中,Intent、Bundle类都使用了原型模式,实现了clone方法。 我们看下Intent的clone方法。
frameworks/base/core/java/android/content/Intent.java
@Override
public Object clone() {
return new Intent(this);
}
7 总结
原型模式本质上就是对象的拷贝,有浅拷贝和深拷贝之分。使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率。另一个用途就是保护性拷贝,也就是某个对象对外可能是只读的,为了防止外部对这个只读对象修改,通常可以通过返回一个对象拷贝的形式实现只读的限制。需要注意的是,拷贝对象时,不会再次执行原型对象的构造函数。