何时使用对象引用的深层克隆?或多久一次?

时间:2013-08-02 04:57:50

标签: c# oop object reference deep-copy

我知道什么是deep copyshallow copyhow to deep copy等,但我的主要疑问是何时深层复制对象引用?或多久一次?


情景1:
考虑一下代码,有关完整代码,请参阅http://pastebin.com/WEgeBFNb

class Box{
        Position pos;
        Box(Position p){ 
           pos = p;      
        }
        Position getPosition(){
          return pos;
        }
   }

main()一样:

public class Sample{
      public static void main(String args[]){
        Position pos = new Position(3,5);
        Box box = new Box(pos);     
        pos.setX(5);
        System.out.println( box.getPosition().getX()); 
            // Will print 5, but I want Box to retain its value
      }

我已通过以下方式达到上述要求:

 Box(Position p){ 
           pos = new Position(p);     // Deep cloning 
        }

然后我必须在Position中有一个复制构造函数,例如:

Position(Position p){
     x  = p.x;
     y  = p.y;
   }

但我的问题是:何时使用深度克隆?

场景2: 例如,考虑一个c#代码。

List<Accounts> = Mysession.getAllAccounts();。在这里,我希望返回对象的更改不得反映在会话对象中。 (这种情况不仅在C#中,而且通常在任何oop语言中) 所以,如果我开始深度克隆,那么,这不是一项容易的任务,因为它可以达到5级更深的对象,并且具有关系

再一次,我知道要准确100%我必须深度克隆。同意。

  1. 什么更常见?返回引用或对象的副本?
  2. 我听说,深度克隆是一个繁琐的过程,必须避免它。所以一个人可以深入克隆一次?
  3. 你能给出一些示例场景(不需要代码)。
  4. 在上述box示例的初始化时,必须使用克隆pos = new Position(p)?或正常分配pos = p

2 个答案:

答案 0 :(得分:1)

面向对象编程的主要目的必须是一个对象保证它在任何时候都处于一个leagal状态。

因此,当您返回对象的引用时,您应该考虑:

  1. 返回的对象是不可变的吗?
  2. 返回引用(主对象)的当前对象是否具有依赖于返回对象的值? (派生或缓存的值)
  3. 您可以通过以下方式对这些问题的答案做出反应:

    返回的引用是一个不可变对象(String,BigDecimal等)

    1. 无需任何操作
    2. 返回的引用是一个可变对象(数组,日期等),但主对象没有派生值(例如只装饰它)

      1. 无需任何操作
      2. 返回的引用是一个可变对象(数组,日期等),主对象具有派生值

        1. 在返回对象之前复制该对象。如果副本易于制作且不存储或耗时(取决于您的非功能性要求),则适用此选项。

        2. 返回对原始对象的不可修改的引用(如Collections.unmodifiable ...)。

        3. 返回一个检测对返回对象的访问权限的代理,并通知主对象这些更改,以便主对象可以重新计算派生值,并且不会处于不一致状态。
        4. 在获得对象引用时问自己相同的问题。通过构造函数或方法调用。

答案 1 :(得分:1)

而不是根据“深度”或“浅层”克隆来思考,而是根据每个封装对象引用所代表的内容进行思考。假设某个类实例Foo的字段George包含IList<String>类型的引用。这样的领域至少可以代表五种不同的东西:

  • 对不可变类型实例的引用,用于封装其中包含的字符串。

  • 对一个对象实例的引用,该对象实例的类型可以是可变的,但永远不会暴露给任何可能使其变异的对象,为了封装其中包含的字符串而保留。

  • 在George的方法调用堆栈之外,宇宙中任何地方都会存在唯一一个引用乔治用来封装其状态的可变列表。

  • 对内容可能更改的列表的引用,该列表构成某个其他对象的可变状态的一部分。该字段不用于封装列表的内容,而是用于身份

  • 对内容可能更改的列表的引用,其内容被视为George状态的一部分,以及存在外部持久性引用。

如果Foo属于前两种类型,那么乔治的正确副本可能会使其Foo引用与George.Foo相同的列表,这是一个新构建的列表,它始终是保持相同的内容,或任何其他始终保持相同内容的列表。如果它属于第三种类型,则乔治的正确副本必须使其Foo引用一个新列表,该列表预先加载了George.Foo中项目的副本。如果是第四种类型,则正确的副本必须使其Foo引用同一对象作为George.Foo引用副本。如果是第五种类型,则George无法单独克隆。

如果列表项是可变类型(而不是String),则必须确定应用于列表中包含的项目的五个目的中的哪一个,并将每个列表项视为一个可以处理的项目一个领域。请注意,对于逻辑上不可变的类型,其中包含的任何引用都必须是可共享的。如果对象的正确行为要求它持有引用的某些东西不是任何其他引用的目标,那么这意味着对于持有引用的对象应该只存在一个引用。