原型模式:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
原型模式可能是除了单例模式与迭代器模式之外23种设计模式中最简单的设计模式了。原型模式的核心就是一个clone方法,Java提供了一个Cloneable接口表示这个对象是可拷贝的,然后重写Object类中的clone方法。
Java中原型模式的通用代码:
public class ProtoType implements Cloneable {
@Override
public ProtoType clone() {
ProtoType protoType = null;
try {
protoType = (ProtoType) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return protoType;
}
}
只要实现一个接口然后重写一个方法。
优点
性能优良:原型模式是对内存二进制流的拷贝,比new一个对象快的多。
逃避构造函数的约束:这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。
使用场景
- 资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 - 性能和安全要求的场景
通过new产生-一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 - 一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与Java融为一体, 大家可以随手拿来使用。
注意事项
别看它这么简单,其中的问题可不少。
1、构造函数不会执行
A类实现了Cloneable接口并且重写clone方法,当B类调用A类的clone方法来创建对象时,A类的构造方法不会执行。下面举个实例来论证
A类:
public class ProtoType implements Cloneable {
public ProtoType(){
System.out.println("构造方法执行。。。");
}
@Override
public ProtoType clone() {
ProtoType protoType = null;
try {
protoType = (ProtoType) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return protoType;
}
}
B类
public class Client {
public static void main(String[] args) {
ProtoType protoType = new ProtoType();
ProtoType p1 = protoType.clone();
}
}
结果:
具体来说B类调用A类的clone方法是从堆内存中以二进制流的方法拷贝,重新分配一个内存块,直接就相当于把创建好的对象复制一份。
2、 浅拷贝深拷贝
看完上述的clone过程,你可能会问,如果对象里有引用数据类型呢?堆内存里保存的只是这个属性的引用啊,属性所指向的对象分配在其他堆中,所以Object类提供的clone方法只拷贝对象,其对象内部的数组,引用对象都不拷贝,还是指向原生对象的内部地址,这就叫浅拷贝。
下面举个例子
public class ProtoType implements Cloneable {
private List<String> list = new ArrayList<String>();
@Override
public ProtoType clone() {
ProtoType protoType = null;
try {
protoType = (ProtoType) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return protoType;
}
public List<String> getList() {
return list;
}
public void setList(String s) {
list.add(s);
}
}
测试
public static void main(String[] args) {
ProtoType protoType = new ProtoType();
ProtoType p1 = protoType.clone();
protoType.setList("张三");
System.out.println(protoType.getList());
p1.setList("李四");
System.out.println(p1.getList());
}
输出
调试观察
很明显,两个对象共享一个私有变量,很不安全,这就是浅拷贝。浅拷贝时,内部的数组与引用类型不拷贝,其他的基本类型如char、int、long都会拷贝,但String虽然是引用类型,它也会被拷贝。
深拷贝
深拷贝就是对要拷贝的对象的引用类型也进行拷贝
public class ProtoType implements Cloneable {
private ArrayList<String> list = new ArrayList<String>();
@Override
public ProtoType clone() {
ProtoType protoType = null;
try {
protoType = (ProtoType) super.clone();
protoType.list = (ArrayList<String>) this.list.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return protoType;
}
public List<String> getList() {
return list;
}
public void setList(String s) {
list.add(s);
}
}
结果很明显了
3、clone方法与final关键字会产生冲突
final关键字就是禁止重新赋值的,就算你克隆成功也没办法赋值。解决的方法只能删去关键字。