一种非常常见的C#模式,打破了一个非常基本的OOP原则

时间:2012-07-18 14:59:58

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

这是一个非常简单的问题,我仍然非常不安:

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

public class EncapsulationViolator
{
  private object abuseMe;

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

编辑我正在考虑的案例是

EncapsulationViolator ev = new EncapsulationViolator();

object o = ev.AbuseMe;

o.SetValue(newValue);

现在,ev的状态因传染性而发生变化,因为其成员滥用状态已发生变化。

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

  

允许外部对象仅保留对根的引用。短暂的   对内部成员的引用可以在一个内部传递出来使用   仅限单一操作。因为root控制访问,所以不能   因内部变化而瞎了。

     

[ Domain-Driven Design ,Eric Evans]

... setters schmetters ......

6 个答案:

答案 0 :(得分:12)

您将C ++术语“引用”与C#按值(其引用)传递对象的事实混淆。

在这种情况下,获取者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!

答案 1 :(得分:10)

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

如果它是一个可变对象,您可以从类外部更改对象的内容,但不能替换该对象本身。

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

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

答案 2 :(得分:8)

我不认为它打破了封装。该类仍然决定AbuseMe的返回值来自何处。它可能来自不同的成员,也可能每次都可以重新创建或复制。

关键是该类决定了它允许用户对该成员执行的操作(获取/设置或两者及其可见性),它可以执行验证并防止设置无效值,并且用户不需要知道价值来自何处。

此外,如果要将自定义逻辑添加到get / set方法,则可以在不破坏与其他程序集的兼容性的情况下执行此操作。

答案 3 :(得分:1)

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

答案 4 :(得分:1)

getter和setter的重点是强制执行封装。重点是您不直接授予对象访问权限,但强制它由您定义的函数访问。吸气剂和制定者是封装。如果您决定只返回该对象,那么这就是您的业务,但是您不允许直接访问而不会遇到吸气剂。

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

答案 5 :(得分:1)

IMO - 这里的答案太多了促进吸气者/制定者。 getters / setter非常适合程序代码,你可以做一些计算并设置结果或获取值并做出决定。

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

话虽这么说,我自己也使用访问者/属性。但是,如果可能的话,我会尽量避免它们。