zoukankan      html  css  js  c++  java
  • 创建对象_原型(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. 序列化和反序列化比较耗时。
     
     
    总结:使用原型模式有以下优点:
    • 创建大的聚合对象图时,没有必要为每个层次的子对象创建相应层次的工厂类。
    • 方便实例化,只要复制对象,然后初始化对象,就可以得到想要的对象。
     
     
  • 相关阅读:
    斐波那契数列 的两种实现方式(Java)
    单链表反转
    单链表合并
    两个有序list合并
    list去重 转载
    RemoveAll 要重写equals方法
    Java for LeetCode 138 Copy List with Random Pointer
    Java for LeetCode 137 Single Number II
    Java for LeetCode 136 Single Number
    Java for LeetCode 135 Candy
  • 原文地址:https://www.cnblogs.com/lindu/p/3186587.html
Copyright © 2011-2022 走看看