建立对象_原型(Prototype)模式_深拷贝

 
举例
    刚给个人客厅作了装修,朋友也但愿给他的客厅作装修,他可能会把我家的装修方案拿过来改改就成,个人装修方案就是原型。
 
定义
    使用原型实例指定将要建立的对象类型,经过复制这个实例建立新的对象。
 
应用场景
    当建立一些很是耗时的大对象或者建立过程很是复杂时。
 
 
复制原型对象不必定是指从内存中进行复制,原型数据也可能保存在数据库里。
通常状况下,OOP 语言都提供了内存中对象的复制能力,Java 语言提供了对象的浅拷贝。
 
 
浅拷贝(Shallow copy):复制一个对象时,若是它的一个属性是引用,则复制这个引用,使之指向内存中同一个对象;
深拷贝(Deep copy):复制一个对象时,为此属性建立了一个新对象,让其引用指向它。
 
邮递快递的场景:
顾客:“给我几个快递。”
快递员:“寄往什么地方?寄给...?”
顾客:“和上次差很少同样,只是邮寄给另一个地址,这里是邮寄地址...“把邮寄地址的纸条给快递员。
快递员:“好。”
    觉得保存了用户之前的邮寄信息,只要复制这些数据,而后经过简单的修改就能够快速地建立新的快递数据了。
 
注意:咱们在复制新的数据时,须要特别注意不能把全部数据都复制过来,如,当对象包含主键时,不能使用原型数据的主键,必须建立一个新的主键。
 
 
Java 的 java.lang.Object 方法里就提供了克隆方法 clone( ),原则上彷佛全部类都拥有此功能,可是它的使用有以下限制:
  1. 要实现克隆,必须实现 java.lang.Cloneable 接口,不然在运行时调用 clone( ) 方法,会抛出 CloneNotSupportedException异常。
  2. 返回的是 Object类型的对象,因此使用时可能须要强制类型转换。
  3. 该方法是 protected的,若是想让外部对象使用它,必须在子类重写该方法,设定其访问范围是 public的,参见 PackageInfo 的 clone( ) 方法。
  4. Object 的 clone( ) 方法的复制是采用逐字节的方式从内存赋值数据,复制了属性了引用,而属性所指向的对象自己没有被复制,所以所复制的引用指向了相同的对象。即 浅拷贝。
 
public   class  PackageInfo  implements  Cloneable {
     public   PackageInfo  clone() {
         try  {
             return  (PackageInfo)  super .clone();
        }  catch  (CloneNotSupportedException e) {
            System. out .println( "Cloning not allowed." );
             return   null ;
        }
    }

     // 静态工厂方法:根据原型建立一份副本
     public   static  PackageInfo clonePackage(String userName) {
         // 根据 userName加载一条用户之前的数据做为原型数据(数据库或其它保存的数据)
        PackageInfo prototype =  loadPackageInfo (userName);
         // 再在内存中克隆这条数据
        prototype =  prototypr .clone();
         // 初始化数据 id(主键)
        prototype. setId ( null );
         // 返回数据
         return  prototype;
    }
}
 
    在实际应用中,使用原型模式建立对象图(Object Graph)很是便捷。
对象图不是一个单个对象,而是一组聚合的对象,改组对象有一个根对象。
 
 
 
 
深拷贝(Deep copy)的两种实现方式:
  1. 复制对象时,递归地调用属性对象的克隆方法。根据具体的类,撰写出实现特定类型的深拷贝方法。
        通常咱们很难实现一个通常性的方法来完成任何类型对象的深拷贝。根据反射获得属性的类型,而后依照它的类型构造对象,但前提是:这些属性的类型必须含有一个公有的默认构造方法,不然做为一个通常性的方法,很难肯定传递给非默认构造方法的参数值;此外,若是属性类型是接口或者抽象类型,必须提供查找到相关的具体类方法,做为一个通常性的方法,这个也很难办到。
     
  2. 若是类实现了 java.io.Serializable 接口,把原型对象序列化,而后反序列化后获得得对象,其实就是一个新的深拷贝对象。
 
//DeepCopyBean实现了 java.io.Serializable接口
public   class   DeepCopyBean   implements  Serializable {
     // 原始类型属性
     private   int   primitiveField ;
     // 对象属性
     private  String  objectField ;
     // 首先序列化本身到流中,而后从流中反序列化,获得得对象即是一个新的深拷贝
     public  DeepCopyBean deepCopy() {
         try  {
            ByteArrayOutputStream buf =  new  ByteArrayOutputStream();
            ObjectOutputStream o =  new  ObjectOutputStream(buf);
            o.writeObject( this );
            ObjectInputStream in =  new  ObjectInputStream(
                     new  ByteArrayInputStream(buf.toByteArray()));
             return  (DeepCopyBean) in.readObject();
        }  catch  (Exception e) {
            e.printStackTrace();
        }
         return   null ;
    }
    // 属性 get、set 方法略...
 
 
    // 测试demo
     public   static   void  main(String[] args) {
        DeepCopyBean originalBean =  new  DeepCopyBean();
         // 建立两个 String对象,其中一个在 JVM的字符串池(String pool)里,属性引用指向另一个在堆里的对象
        originalBean.setObjectField( new  String( "guilin" ));
        originalBean.setPrimitiveField(50);
         // 深拷贝
        DeepCopyBean newBean = originalBean.deepCopy();
         // 原始类型属性值比较:true
        System. out .println( "primitiveField ==:"
                + (originalBean.getPrimitiveField() == newBean
                        .getPrimitiveField()));
         // 对象属性值比较:false(证实未指向相同的地址)
        System. out .println( "objectField ==:"
                + (originalBean.getObjectField() == newBean.getObjectField()));
         // 对象属性 equals 比较:true
        System. out .println( "objectField equal:"
                + (originalBean.getObjectField().equals(newBean
                        .getObjectField())));
    }
}

 

使用这种方式进行深拷贝注意:
  1. 它只能复制实现 Serializable接口类型的对象,其属性也是可序列化的;
  2. 序列化和反序列化比较耗时。
 
 
总结:使用原型模式有如下优势:
  • 建立大的聚合对象图时,没有必要为每一个层次的子对象建立相应层次的工厂类。
  • 方便实例化,只要复制对象,而后初始化对象,就能够获得想要的对象。
相关文章
相关标签/搜索