我目前正在努力理解我刚刚看到的某些地方。
假设我有两个班级:
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
答案 0 :(得分:2)
MySecondClass上的FirstClassObject属性没有setter这一事实并不意味着从getter返回的对象变为不可变。由于它有公共字段,因此这些字段是可变的。因此,secondClassObject.FirstClassObject.membVar1 = 42
是完全合法的。缺少setter只意味着您不能使用对不同对象的引用替换存储在firstClassObject字段中的对象引用。
答案 1 :(得分: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";
这就是我写它的方式。这是明确的,可以理解的。
答案 2 :(得分:0)
类型MyFirstCLass
的存储位置和类型为MyFirstCLass
的属性返回的值>都不包含字段membVar1
,membVar2
等。存储位置或相反,属性包含足以标识 MyFirstCLass
实例或指示它为“null”的信息。在某些语言或框架中,存在标识对象但仅允许对其执行某些操作的引用类型,但Java和.NET都使用混杂对象引用:如果对象允许包含引用的外部代码执行某些操作它,任何获得引用的外部代码都能够做到这一点。
如果一个类正在使用一个可变对象来封装它自己的状态,并且希望允许外部世界看到该状态但不允许外部世界篡改它,则它不能将该对象直接返回到外部代码而是给外面的代码别的东西。可能性包括:
公开对象所包含的状态的所有方面(例如,具有membVar1
属性,该属性返回封装对象的membVar1
的值)。这可以避免混淆,但为调用者提供无法作为一个组处理属性的方法。
返回一个只读包装器的新实例,该包装器包含对私有对象的引用,并具有将读取请求(但不是写入请求)转发给这些成员的成员。返回的对象将作为只读“视图”,但外部代码没有很好的方法来识别两个这样的对象是否是同一底层对象的视图。
拥有一个只读包装类型的字段,该字段在构造函数中初始化,并具有返回该属性的属性。如果每个对象只有一个与之关联的只读包装器,则只有两个包装器引用标识相同的包装器时才会查看它们。
创建基础数据的不可变副本,可能是通过创建新的可变副本并向其返回新的只读包装。这将为调用者提供数据的“快照”,而不是实时“视图”。
创建基础数据的新可变副本,并返回该副本。这样做的缺点是,尝试通过更改副本来更改基础数据的调用者将被允许更改副本而不会发出任何警告,但操作将不起作用。为什么可变结构为“邪恶”的所有论据在这里都是双重的:接收暴露字段结构的代码应该期望对接收到的结构的改变不会影响它来自的来源,但接收可变类对象的代码无法知道。属性不应该这样;这种行为通常只适用于明确其意图的方法(例如FirstClassObjectAsNewMyFirstClass();
要求调用者传入可以接受基础数据的类型的可变对象,并将数据复制到该对象中。这为调用者提供了可变形式的数据(在某些情况下可能更容易使用),但同时避免了对谁“拥有”该对象的任何混淆。作为额外的好处,如果调用者将进行许多查询,调用者可以为所有调用重用相同的可变对象,从而避免不必要的对象分配。
将数据封装在结构中,并使属性返回结构。有些人可能不愿意这样使用,但在调用者可能想要分段修改数据的情况下,这是一个有用的约定。如果所讨论的数据仅限于一组固定的离散值(例如矩形的坐标和维度),这种方法才有效,但如果调用者了解.NET结构是什么(因为所有.NET程序员都应该)语义本质上是显而易见的。
在这些选择中,只有最后两个通过类型系统明确了调用者应该期望的语义。从调用者接受一个可变对象提供了清晰的语义,但使用起来很尴尬。返回暴露字段结构提供了清晰的语义,但前提是数据由一组固定的离散值组成。返回数据的可变副本有时很有用,但只有在方法名称明确它正在做什么时才适用。其他选择通常会使数据代表快照或实时“视图”的问题含糊不清。