定义
原型模式(Prototype Pattern)是一种对象创建型模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
模式结构
Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是抽象类也可以是接口,甚至还可以是具体实现类。
ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
Client(客户类):在客户类中,让一个原型对象克隆自身从而创建一个新的对象,只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。
适用场景
1、资源优化场景。
2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
3、性能和安全要求的场景。
4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
5、一个对象多个修改者的场景。
6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。
代码实现
原型模式有两种实现方式——浅拷贝和深拷贝。
浅拷贝
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
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();
}
}
测试结果:
public class ShallowTest {
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);
}
}
控制台打印结果:
Student{name='zhangsan', age=13, addr=Address{addrName='China'}}
Student{name='zhangsan', age=13, addr=Address{addrName='China'}}
Student{name='lisi', age=13, addr=Address{addrName='Canada'}}
Student{name='zhangsan', age=13, addr=Address{addrName='Canada'}}
深拷贝
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
注意:深拷贝有两种实现方式
1、重写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();
}
优缺点
优点:
1、性能提高。
2、逃避构造函数的约束。
缺点:
1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
2、必须实现 Cloneable 接口。