当我们已经拥有某个得来不易的宝贝时,往往我们会很想再“变”一些出来,即这个宝贝的“复制品”,这种方式简单又理想,谁都想要学会这项本事。不可能的事情!不过,这种手段在 软件设计中是完全可以实现的,在OO中的原型模式就是这样基于思想的。

原型模式的适用场景:(摘录自《设计模式迷你手册》)

1、 当要实例化的类是在运行时刻指定时,例如,通过动态装载;

2、 为了避免创建一个与产品类层次平行的工厂类层次时;

3、 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

通用类图如下:

在 Java中,原型模式可以很简单地实现,只要实现Cloneable这个标识性的接口,再覆盖该接口中的clone()方法,即可“克隆”该实现类的任何一个对象。“克隆”的意思大家都明白,就是原封不动的复制。

由于Java中的最基类Object类中已经实现了Cloneable接口,故我们下面的代码例子中的会看不到上面类图中Prototype这个抽象类的影子。

具体代码如下:

测试结果:

蚂蚁 ...

蚂蚁 ...

   在Java中有个浅拷贝和深拷贝之分,下面再给出个代码例子。
//原型02,成员变量中包含引用变量,得用深拷贝 
class ConcretePrototype02  implements Cloneable { 
   private String name; 
   private ArrayList<String> nameList =  new ArrayList<String>(); 

   public ConcretePrototype02(String name) { 
     this.name = name; 
     this.nameList.add( this.name); 
  } 
   //添加nameList中的对象 
   public  void setName(String name) { 
     this.nameList.add(name); 
  } 
    
   public ArrayList<String> getNameList() { 
     return  this.nameList; 
  } 
    
   //覆盖Object基类中的clone()方法,并扩大该方法的访问权限,具体化返回本类型 
   public ConcretePrototype02 clone() { 
    ConcretePrototype02 self =  null
     try { 
      self = (ConcretePrototype02)  super.clone(); 
       //以下这句是实现深拷贝的关键 
//      self.nameList = (ArrayList<String>) this.nameList.clone(); 
    }  catch (CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
     return self; 
  } 


//测试类 
public  class Client { 
   public  static  void main(String[] args) { 
    ConcretePrototype02 prototype02 =  new ConcretePrototype02( "蚂蚁 ..."); 
    System.out.println(prototype02.getNameList()); 
     
     //通过clone获得一个拷贝 
    ConcretePrototype02 fromClone02 = prototype02.clone(); 
    fromClone02.setName( "小蚂蚁 ..."); 
    System.out.println(fromClone02.getNameList()); 
    System.out.println(prototype02.getNameList()); 
  } 
}
测试结果:

拷贝之前的原型: [蚂蚁 ...]

拷贝得到的对象: [蚂蚁 ..., 小蚂蚁 ...]

拷贝之后的原型: [蚂蚁 ..., 小蚂蚁 ...]

发现拷贝之后原来的对象持有的 ArrayList<String> 类型的 nameList引用会随着拷贝得到的fromClone对象执行了setName()方法而改变,这不是我们想要的结果,因为这意味着原型以及拷贝得到的对象共享同一个引用变量,这是线程不安全的。当我们去掉上面clone()方法中被注释的语句之后再测试,得到结果如下:

拷贝之前的原型: [蚂蚁 ...]

拷贝得到的对象: [蚂蚁 ..., 小蚂蚁 ...]

拷贝之后的原型: [蚂蚁 ...]

结果正确。其实,在Java中使用原型模式Prototype是相当简单的,只要记住几点注意点,就可以方便地实现该模式了。由于使用clone()方法来拷贝一个对象是从内存二进制流中进行IO读写,所以拷贝得到一个对象是不会执行该对象所对应类的构造函数的。总结如下:

1、 构造函数不会被执行;

2、 类的成员变量中若有引用类型的变量(数组也是一种对象),默认的clone()并不会对其进行拷贝,需自行提供深拷贝;

String类型与int、long、char等基本类型类似,默认地会被拷贝。
     总之,Java中原型模式clone()方法对我们隐藏了许多细节,或者说必要操作,它的实现机制涉及到了反射、IO流操作、序列化等,只有弄清楚这一系列的知识才能更深入地理解这些相关的知识点。
//原型01,实现Cloneable接口并覆盖clone()方法 
class ConcretePrototype01  implements Cloneable { 
   private String name; 

   public ConcretePrototype01(String name) { 
     this.name = name; 
  } 
    
   public  void getName() { 
    System.out.println(name); 
  } 
    
   //覆盖Object基类中的clone()方法,并扩大该方法的访问权限,具体化返回本类型 
   public ConcretePrototype01 clone() { 
    ConcretePrototype01 self =  null
     try { 
      self = (ConcretePrototype01)  super.clone(); 
    }  catch (CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
     return self; 
  } 


//测试类 
public  class Client { 
   public  static  void main(String[] args) { 
    ConcretePrototype01 prototype01 =  new ConcretePrototype01( "蚂蚁 ..."); 
    prototype01.getName(); 
     
     //通过clone获得一个拷贝 
    ConcretePrototype01 fromClone01 = prototype01.clone(); 
    fromClone01.getName(); 
  } 
}