原型模式属于对象创建模式,GOF 给它的定义为:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
(一)概念梳理
浅复制与深复制概念
1.浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
2.深复制(深克隆) 被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
(二)原型模式的组成结构
1) 客户角色:让一个原型克隆自己来得到一个新对象。
2) 抽象原型角色:实现了自己的 clone 方法,扮演这种角色的类通常是抽象类,且它具有
许多具体的子类。
3) 具体原型角色:被复制的对象,为抽象原型角色的具体子类。
类图:
(三)原型模式的示例代码
(3.1)通过反射实现克隆
抽象原型角色 – Prototype
public abstract class Prototype {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract Prototype cloneMe() throws Exception;
}
具体原型角色 – ConcretePrototype
public class ConcretePrototype extends Prototype {
@Override
public Prototype cloneMe() throws Exception{
Prototype prototype = new ConcretePrototype ();
/**1.getFields只能获得类中公共(public)的字段,包含父类中的公共(public)的字段**/
/**2.getDeclaredFields获得类中所有的字段,包括共有字段和私有字段,默认字段和受保护字段。
但是不能获得父类中所有的字段**/
for(Field f: Prototype.class.getDeclaredFields ()){
String methodName = f.getName ().substring(0,1).toUpperCase()+
f.getName ().substring(1); //将属性的首字符大写,方便构造get,set方法
// 获取属性的类型
/**若是int类型的id字段,type.getClass()是java.lang.Class的Class对象,
而下面语句得到的是int的Class对象**/
Class type = (Class)f.getGenericType();
//执行get方法
Object value = prototype.getClass ().getMethod ("get" + methodName).invoke (this, null);
//执行set方法,参数类型必须传入
Prototype.class.getDeclaredMethod ("set" +methodName, type)
.invoke (prototype, value);
}
return prototype;
}
}
客户端调用 – MAIN
public class Main {
public static void main(String[] args) throws Exception{
Prototype prototype = new ConcretePrototype ();
prototype.setId (1);
prototype.setName ("简历");
Prototype copy = prototype.cloneMe ();
System.out.println (copy.getId () + "-" + copy.getName ());// 1-简历
}
}
(3.2)通过JAVA原生的clone()实现
Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的,所以实体类使用克隆的前提是:
① 实现Cloneable接口,这是一个标记接口,自身没有方法。
② 覆盖clone()方法,可见性提升为public。
public class ClonePrototype implements Cloneable{
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException{
return super.clone ();
}
}
测试:
public class Main {
public static void main(String[] args) throws Exception{
ClonePrototype clonePrototype = new ClonePrototype ();
clonePrototype.setId (1);
clonePrototype.setName ("clone");
ClonePrototype copyClone = (ClonePrototype) clonePrototype.clone ();
System.out.println (copyClone.getId () + "-" + copyClone.getName ());//1-clone
}
}
注:这样只是浅复制,若需要原型和复制品中的引用类型的属性不指向同一内存空间,需要将复制品中的该属性也克隆一遍:
copyProto.referenceObj = proto.referenceObj.clone();