【问题标题】:A very common C# pattern that breaks a very fundamental OOP principle一个非常常见的 C# 模式,它打破了一个非常基本的 OOP 原则
【发布时间】:2012-07-17 15:57:03
【问题描述】:

这是一个很简单的问题,我还是很不安:

为什么现在广泛接受一个类通过访问器方法返回对其私有成员的引用?这不是完全破坏了封装原则吗?如果可以,那为什么不把会员公开!?

public class EncapsulationViolator
{
  private object abuseMe;

  public object AbuseMe 
  {
    get { return abuseMe; }
  }
}

编辑我考虑的情况是这样的

EncapsulationViolator ev = new EncapsulationViolator();

object o = ev.AbuseMe;

o.SetValue(newValue);

现在 ev 的状态通过传递性发生了变化,因为它的成员 abuseMe 的状态发生了变化。

在 DDD 的上下文中,如果对象是聚合根,则这是不行的。我引用

只允许外部对象保存对根的引用。短暂的 可以传递对内部成员的引用以供在 仅限单次操作。因为root控制访问,所以不能 内部结构的变化让我们措手不及。

[Domain-Driven Design,埃里克·埃文斯]

... setters schmetters ...

【问题讨论】:

  • 没有二传手,所以没有太多滥用的余地
  • 有时人们使用这种方法在return 被解雇之前做一些事情,这基本上是一个公共领域,但人们需要在设置/获取之前做一些事情。此外,getter 和 setter 在 java 中也被广泛使用。
  • 布拉德:我的观点完全正确。一旦调用者在调用 AbuseMe 后获得了引用,他们现在就可以更改 abuseMe 了,对吧?
  • @ForteL.,他们可以改变被引用对象的属性。
  • @ForteL.: 如​​果 prop 返回一个实现 IDisposable 的对象,您可以销毁该对象。

标签: c# oop encapsulation solid-principles design-principles


【解决方案1】:

您将 C++ 术语“引用”与 C# 传递对象按值(它们的引用)这一事实混为一谈。

在这种情况下,getter AbuseMe 的调用者不能换出私有字段abuseMe。因此,没有违反封装。

EncapsulationViolator x = new EncapsulationViolator();
object y = x.AbuseMe;
y = 17; // I have not changed x.AbuseMe

Debug.Assert(y != x.AbuseMe); // Passes!

此外,属性 getter 和 setter 允许对私有字段进行适当封装,并且在功能上与将它们实现为方法相同(实际上它们由编译器实现为方法)。

返回私有变量可能破坏封装的一种情况是当您返回对数组的引用时:

class X
{
    private int[] amazing = new int[10];

    public int[] Amazing { get { return this.amazing; } }
}

X a = new X();
int[] x = a.Amazing;
int[] y = a.Amazing;

x[2] = 9;
Debug.Assert(x[2] != y[2]); // Fails!

【讨论】:

  • 六:我想数组案例是我在考虑违规时正在考虑的案例,您说这是规则的例外,对吗?但是,我想我可能对封装有一个根本的误解;当通过访问器 (get{}/get()) 返回引用时,调用者在其上调用公共方法,更改其状态,现在在被调用的类 b/c 中也更改了状态,它们共享引用,它不被视为违反封装?
  • @SamusArin:如果您的属性是SomeType 类型,您是否真的需要在OwningType 上实现方法来修改SomeTypes 属性中的每一个(以及等等等等)?如果您在 getter 中返回类引用,那么您将依赖该类的适当“封装”。
  • 六:好点。你真的帮助我理清了这个基本 oop 属性的概念,谢谢。
  • 你错了。 C# 不通过引用传递对象。对象引用是按值传递的,除非您专门使用 ref 或 out 关键字通过引用传递引用。
  • 我的意思是“他们的参考价值”?我避免使用“指针”。谢谢!
【解决方案2】:

这取决于成员是什么类型的对象。例如,如果它是一个字符串,那么它是不可变的,所以你不能更改字符串。

如果是可变对象,可以从类外改变对象的内容,但不能替换对象本身。

如果不能从类外部更改对象,则 getter 应返回对象的不可变版本。

如果你做错了模式可能会破坏封装,但如果做得正确,封装是完整的。

【讨论】:

  • Guffa:通过“如果它是一个可变对象,你可以从类外部更改对象的内容,但你不能替换对象本身。”,你是说你可以修改对象(即滥用我)的状态(通过其公共方法),但不能更改引用指向的位置?
  • 好的,今天你的回答对我来说更有意义,对我有很大帮助。谢谢!
【解决方案3】:

我认为它不会破坏封装。类仍然决定AbuseMe 的返回值来自哪里。它可能来自不同的成员,也可能每次都重新创建或复制。

重点是类决定它允许用户对该成员做什么(获取/设置或两者及其可见性),它可以执行验证并防止设置无效值,用户不需要知道该值来自哪里。

此外,如果您想向 get/set 方法添加自定义逻辑,您可以这样做而不会破坏与其他程序集的兼容性。

【讨论】:

  • +1 表示“不破坏与其他程序集的兼容性”
  • Botz:都是真的,但是我说的是这个特定的案例,而不是一般的 get/set 访问器的实用性。
  • @SamusArin 在这种特殊情况下,我想说它是为了强制执行只读访问。用户可以修改对象,但不能更改对象引用。如果全班认为没问题,那就没问题了。
  • Botz:好吧,我想这是合理的。
  • Botz:现在我对封装的微妙误解已经被清除了,我看到这是一个非常简洁明了的答案,谢谢(我真的希望我能接受多个答案,b /c 我也肯定会接受这个...必须把它交给六个字母的变量,尽管 b/c 他向我展示了这一点)。
【解决方案4】:

这只是语法糖。它与 Java 的 getXXX 和 setXXX 方法没有什么不同。

【讨论】:

  • ...还是不行!
  • 这不仅仅是糖。字段和属性之间存在差异。一个区别是序列化处理字段与属性的方式。
  • @Boo 这是为字段创建 get/set 方法的语法糖。
  • @AlexanderR:嗯,不。同样,您不能序列化方法。我了解访问器是如何实现为函数的,但它们的处理方式不同,因此不仅仅是糖。
【解决方案5】:

getter 和 setter 的重点是强制封装。关键是您不直接访问对象,而是强制您定义的函数访问它。 getter 和 setter 是封装。如果您决定只返回对象,那是您的事,但您不允许在不点击 getter 的情况下直接访问。

阅读: http://en.wikipedia.org/wiki/Mutator_method

【讨论】:

  • 我明白你的意思,而且总的来说非常正确,但是上面的具体示例确实传递了“原始”引用,我一遍又一遍地看到这一点,即使在专家的示例中也是如此。
  • Giovanni - 读得好,我一直想知道如何在成员函数中使用访问器方法,或者直接访问私有成员(以及性能差异)。那篇文章涵盖了所有这些内容,谢谢!
【解决方案6】:

IMO - 这里有太多的答案在宣传 getter/setter。 Getter/setter 非常适合程序代码,您要么进行一些计算并设置结果,要么获取值并做出决定。

OO 编程中一个众所周知的原则是Tell don't ask,它基本上说您不应该要求对象的内部状态来做出决定。

话虽如此,我自己使用访问器/属性。但是,如果可能,我会尽量避免它们。

【讨论】:

  • Roger:对“告诉不问”语句的有趣思考。需要考虑一下(尽管我想到的最初想法是“副作用”-您的下一个决定基于先前的结果,这似乎与您在此处提出的建议相反)。感谢您的洞察力。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多