【问题标题】:When to use a deep clone of an object reference? or How often?何时使用对象引用的深层克隆?或多久一次?
【发布时间】:2013-08-03 06:17:35
【问题描述】:

我知道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 级的对象,并具有 has-a 关系

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

  1. 什么更常见?返回引用或对象的副本?
  2. 我听说,深度克隆是一个繁琐的过程,必须避免。那么多久可以进行一次深度克隆?
  3. 能否提供一些示例场景(不需要代码)。
  4. 在像上面box 示例中进行初始化时,必须使用克隆pos = new Position(p)?还是正常分配pos = p

【问题讨论】:

  • 如果您的值对象仅包含 System.ValueType 作为字段,您可以使用 struct 而不是 class.so 在这种情况下您可以简单地使用 pos=p 而不是 pos = new Position(p)。跨度>
  • @Frank59 考虑另一个类,它与另一个对象有has-a 关系。在这里说 `new Room(myroom)` Room 包含一个 Box。在这种情况下,在 Room 构造函数中我必须这样做,this.box = new Box(room.getBox()); [深度克隆] 还是简单地 this.box = room.getBox() [参考复制]?
  • 如果你只使用 structs (Box is struct) 和 ValueTypes(Box fields are value types and structs ) 就可以使用 neBox=oldBox; (自动深拷贝),但如果你有 Box 作为类或有对 Box 字段的引用,你需要实现深拷贝算法。阅读更多msdn.microsoft.com/en-us/library/saxz13w4.aspx

标签: c# oop object reference deep-copy


【解决方案1】:

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

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

  1. 返回的对象是不可变的吗?
  2. 返回引用的当前对象(主对象)是否具有依赖于返回对象的值? (派生或缓存值)

您可以通过以下方式对这些问题的答案做出反应:

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

  1. 无需任何操作

返回的引用是一个可变对象(数组、日期等),但主对象没有派生值(例如只装饰它)

  1. 无需任何操作

返回的引用是可变对象(数组、日期等)且主对象有派生值

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

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

  3. 返回一个代理,该代理检测对返回对象的访问并将这些更改通知主对象,以便主对象可以重新计算派生值,并且不会处于不一致状态。

当您获得对象引用时,问自己同样的问题。通过构造函数或方法调用。

【讨论】:

    【解决方案2】:

    不要考虑“深”或“浅”克隆,而是考虑每个封装对象引用所代表的内容。假设某个类实例George 的字段Foo 拥有IList<String> 类型的引用。这样的字段至少可以代表五种不同的事物:

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

    • 对对象实例的引用,其类型可以是可变的,但永远不会暴露给任何可能改变它的对象,其保存目的是为了封装其中包含的字符串。

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

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

    • 对列表的引用,该列表的内容可能会改变,其内容被认为是 George 状态的一部分,并且存在外部持久引用。

    如果Foo 属于前两种类型,则George 的正确副本可能使其Foo 引用与George.Foo 相同的列表,即始终包含相同内容的新建列表,或者任何其他将始终包含相同内容的列表。如果是第三种类型,George 的正确副本必须让其Foo 引用一个新列表,该列表预加载了George.Foo 中的项目副本。如果它是第四种类型,则正确的副本必须让其FooGeorge.Foo 引用相同的对象,并且 引用副本。如果是第五类,George不能单独克隆。

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

    【讨论】:

      猜你喜欢
      • 2016-12-20
      • 2019-09-06
      • 2013-10-11
      • 2010-09-09
      • 2020-03-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多