1

我目前正在努力理解我刚刚在某处看到的东西。

可以说我有两个课程:

 class MyFirstCLass{
      public int membVar1;
      private int membVar2;
      public string membVar3;
      private string membVar4; 

      public MyFirstClass(){
      }
 }

和 :

 class MySecondClass{
      private MyFirstClass firstClassObject = new MyFirstClass();

      public MyFirstClass FirstClassObject{
           get{
                return firstClassObject;
           }
      }
 }

如果我做这样的事情:

 var secondClassObject = new MySecondClass(){
      FirstClassObject = {membVar1 = 42, membVar3 = "foo"}
 };

secondClass 是 MySecondClass 的一个实例,并且确实有一个 MyFirstClass 类型的私有成员变量,它有一个 readOnly 属性。但是,我可以更改 membVar1 和 membVar2 的状态。没有封装问题吗?

最好的祝福,

Al_th

4

3 回答 3

2

MySecondClass 上的 FirstClassObject 属性没有 setter 的事实并不意味着从 getter 返回的对象变得不可变。由于它具有公共字段,因此这些字段是可变的。因此,这是完全合法的说法secondClassObject.FirstClassObject.membVar1 = 42。缺少 setter 仅意味着您不能将存储在 firstClassObject 字段中的对象引用替换为对不同对象的引用。

于 2012-08-21T09:42:30.317 回答
1

请注意:您没有更改MySecondClass.FirstClassObject. 您只是在更改该属性内的值。

比较以下两个片段。第一个是合法的,第二个是不合法的,因为它试图为FirstClassObject属性分配一个新值:

// legal:
var secondClassObject = new MySecondClass(){ 
  FirstClassObject = {membVar1 = 42, membVar3 = "foo"} }

// won't compile:
// Property or indexer 'FirstClassObject' cannot be assigned to -- it is read only
var secondClassObject = new MySecondClass(){ 
  FirstClassObject = new MyFirstClass {membVar1 = 42, membVar3 = "foo"} }

基本上,您的代码只是一种非常奇特的编写方式:

var secondClassObject = new MySecondClass();
secondClassObject.FirstClassObject.membVar1 = 42;
secondClassObject.FirstClassObject.membVar3 = "foo";

我就是这样写的。它是明确的和可以理解的。

于 2012-08-21T09:46:30.380 回答
0

type 的存储位置和 typeMyFirstCLass的属性返回的值都不MyFirstCLass包含 fieldsmembVar1membVar2。存储位置或属性包含足以识别实例MyFirstCLass或指示其为“null”的信息。在某些语言或框架中,存在标识对象但仅允许对其执行某些操作的引用类型,但 Java 和 .NET 都使用混杂对象引用:如果对象允许持有引用的外部代码执行某些操作它,任何获得引用的外部代码都可以做到这一点。

如果一个类正在使用一个可变对象来封装自己的状态,并希望允许外界看到该状态但不允许外界篡改它,那么它一定不能将对象直接返回给外部代码,而是给出外部代码别的东西。可能性包括:

  • 单独暴露对象所包含的状态的所有方面(例如,具有membVar1返回封装对象的值的属性membVar1)。这可以避免混淆,但使调用者无法将属性作为一个组来处理。

  • 返回一个只读包装器的新实例,该实例包含对私有对象的引用,并具有将读取请求(但不是写入请求)转发给这些成员的成员。返回的对象将用作只读“视图”,但外部代码将没有很好的方法来识别两个这样的对象是否是同一个底层对象的视图。

  • 有一个在构造函数中初始化的只读包装类型的字段,并有一个属性返回。如果每个对象只有一个与之关联的只读包装器,则两个包装器引用只有在它们标识相同的包装器时才会查看相同的包装器对象。

  • 创建基础数据的不可变副本,可能通过创建新的可变副本并向其返回新的只读包装器。这将为调用者提供数据的“快照”,而不是实时“视图”。

  • 创建基础数据的新可变副本,并将其返回。这样做的缺点是,尝试通过更改副本来更改基础数据的调用者将被允许更改副本而不会发出任何警告,但该操作将不起作用。为什么可变结构是“邪恶”的所有论点在这里都适用:接收暴露字段结构的代码应该期望对接收结构的更改不会影响它的来源,但接收可变类的代码对象无法知道这一点。属性不应以这种方式运行;这种行为通常只适用于明确其意图的方法(例如FirstClassObjectAsNewMyFirstClass();

  • 要求调用者传入一个可以接受基础数据的类型的可变对象,并将数据复制到该对象中。这为调用者提供了可变形式的数据(在某些情况下可能更容易使用),但同时避免了关于谁“拥有”对象的任何混淆。作为额外的好处,如果调用者将进行许多查询,则调用者可以为所有查询重用相同的可变对象,从而避免不必要的对象分配。

  • 将数据封装在结构中,并让属性返回结构。有些人可能会拒绝这种用法,但在调用者可能想要分段修改数据的情况下,这是一个有用的约定。这种方法只有在所讨论的数据仅限于一组固定的离散值(例如矩形的坐标和尺寸)时才真正有效,但如果调用者了解 .NET 结构是什么(所有 . NET 程序员应该) 语义本质上是显而易见的。

在这些选择中,只有最后两个通过类型系统明确调用者应该期望什么语义。接受来自调用者的可变对象提供了清晰的语义,但使用起来很尴尬。返回一个暴露的字段结构提供了清晰的语义,但前提是数据由一组固定的离散值组成。返回数据的可变副本有时很有用,但只有在方法名称清楚地表明它在做什么时才合适。其他选择通常会模糊数据是代表快照还是实时“视图”的问题。

于 2013-12-29T17:33:50.830 回答