干净的代码:对象应该具有公共属性吗?

27
我正在阅读《Clean Code》这本书,但在一个概念上有些困惑。当讨论对象和数据结构时,它指出如下:
  • 对象通过抽象隐藏其数据,并公开操作该数据的函数。
  • 数据结构公开其数据,没有实际意义上的函数。
因此,我理解为我的对象不应该有任何公共属性,我只应该有执行操作的方法。如果确实需要访问属性,则它们应该是数据结构中的一部分,并可以从我的对象方法中返回。采用这种方法,似乎我需要一个 GetHeight() 和 SetHeight() 方法来获取和设置对象的 Height 属性,而不仅仅使用属性的 get 和 set。
也许我没有完全理解建议的含义,但这就是我对"对象隐藏它们的数据"的理解。如果您可以帮助我理解这个问题,我将不胜感激!
提前致谢!

2
除了下面的答案之外,混淆可能源于许多语言不支持属性。在这种情况下,您可以选择访问器方法和公共字段,而正确的选择始终是访问器方法。C#没有这个问题,因为它支持属性。 - Stephen Cleary
13个回答

31

确实,C#属性不是数据,而是访问器,所以它是在数据上操作的函数。

应避免公开字段,而不是公开属性。


2
我避免使用DTO,但在特定情况下需要使用它们时,我更喜欢自动属性。 - onof
对于几乎所有的项目来说,没有理由优先选择属性而不是字段,并且有几个原因可以优先选择字段。字段具有以下特点:[1]保证无行为(将字段更改为属性需要重新编译,这是好的);[2]有时更快,从不更慢;[3]代码更短;[4]可以是“readonly”,这比“get”更强的保证。只有在编写公共API时才使用属性,该API需要在未来版本中允许属性具有行为或需要私有setter(但请考虑使用“readonly”)。 - Eamon Nerbonne
那么,在组织类成员的位置时,属性会与其他方法一起排列在首次调用它们的方法下面,对吗? - toddmo
@toddmo 这只是格式问题,不涉及设计。我已经将它们放在一行了。 - onof

21

公共属性是可以的。不需要编写显式的GetHeight()SetHeight()方法,这就是属性的全部意义。在C#中,属性不是数据;最好将其视为一对getter/setter方法。(实际上,属性在生成的IL中被编译为方法。)

数据隐藏是可能的,因为您可以在不更改接口的情况下更改实现。例如,你可以更改

public int Height { get; set; }

进入

public int Height { get { return m_width; } set { m_width = value; } }
如果您决定让您的对象始终保持正方形,使用您的类的代码将不需要进行任何修改。因此,如果您的对象公开属性,则仍然“将其数据隐藏在抽象之后并公开操作该数据的函数”,正如本书建议的那样。

1
读者请注意:高度/宽度违反了Liskov替换原则(来自Robert M. Martin的代码整洁之道)。 - Matheus Valiente Souza
是的,那是一个相当糟糕的例子 :) - Thomas

10

它大多是“属性”这个术语的另一种定义。在C#中,属性不是大多数其他编程语言认为的属性。

例如:
C++中的公共属性是:

class foo
{
  public:
    int x;
};

在C#中对应的术语是公共字段:

class foo
{
  public int x;
}

在C#中,我们所称之为属性(properties)的概念,在其他编程语言中通常被称为setter和getter:

C#:

class foo
{
  public int X { get; set; }
}

对应的 C++ 代码:

class foo
{
  private:
    int x;

  public:
    void setX(int newX) { this->x = newX; }
    int  getX() { return this->x; }
}

简而言之:
C# 属性完全没问题,只是不要盲目地将它们默认为 get set,并且不要将类中的每个数据字段都作为公共属性,考虑一下你的类的用户真正需要了解/更改什么。


10

在你完成阅读《代码整洁之道》后,我建议你阅读鲍勃·马丁的另一本书:

C#敏捷开发:原则、模式与实践

在这本书中,大部分内容都是关于一个案例研究的讨论,鲍勃应用了《代码整洁之道》中讨论的原则。我先读了《代码整洁之道》,但回想起来我认为应该先读《敏捷开发》因为《代码整洁之道》更像是一本日常手册或好的软件开发原则指南。

例如,在《敏捷开发》中使用了以下代码:

public class OrderData
{
public string customerId;
public int orderId;
public OrderData() {}

...

}
以下是对使用公共数据进行验证的内容,这与您的问题有关:
不要因为使用公共数据成员而感到冒犯。这并不是真正意义上的对象,它只是一个数据容器。它没有需要封装的有趣行为。将数据变量设置为私有并提供getter和setter方法将是一种浪费时间的做法。我本来可以使用struct而不是class,但我希望通过引用而不是值传递OrderData。
注:
个人认为Robert Martin(连同Martin Fowler、Michael Feathers等人)在这些书籍中为软件开发者社区做出了巨大的贡献。我认为它们是必读的。

还有《代码整洁之道》(The Clean Coder)这本书,虽然题材不同,但我认为它非常值得一读。 - TrueWill

5

尽管公共属性并不是立即的代码异味,但请考虑本文:

与 Yechiel Kimchi 合作的编码(来自书籍《每个程序员都应该知道的 97 件事》)

“......不要询问一个对象以获取要使用的信息。相反,请求对象使用它已经具有的信息进行工作。”

这并不总是适用(例如,数据传输对象)。我要注意的是不当亲密关系


2
+1 作为我桌上的座右铭!感谢您提供的参考。 - JSprang
这是一种严格的面向对象编程方法。在某些情况下很难实现。考虑采用MVVM模式。 - onof
1
完整的书籍可以通过Github免费获取。https://97-things-every-x-should-know.gitbooks.io/97-things-every-programmer-should-know/en/thing_15/ - susodapop

4

属性实际上是方法。
编译器将属性编译为获取/设置MIL方法。


3

属性本质上是Getter和Setter方法的简写。Getter和Setter方法的目的是让对象处理变量上的任何操作,以便您可以执行任何额外的操作(例如数据验证)而不会导致不良后果。

我认为您可能会被自动属性所困扰,它们没有支持变量,因此看起来像变量本身。


3
该书试图阐述一种理论,即对象不应该暴露类的实际实现方式。在更复杂的对象中,许多内部变量不一定从外部角度传达正确信息,因此应该只有作用于它们的方法。
然而,当你有简单的对象时,这个硬性规则就会破裂。在矩形的情况下,高度和宽度是用户想要知道的基本属性。由于这个实现是直接的,不使用get和set会使您的代码比必要的复杂。

3

在纯面向对象编程中,“真正的对象”必须完全隐藏用于实现其职责的数据。因此,应避免公开内部数据,无论是通过公共字段、公共属性还是公共getter/setter函数来完成。

仅通过属性将访问路由到内部数据并不会隐藏或抽象化内部数据!

回答您的问题: - 如果要编写对象,请避免使用公共属性 - 如果要编写数据结构,则可以使用公共属性(公共字段也可以)


3

下面是需要翻译的内容:

让我们看看这个问题。

虽然公开变量有时会很有用,但通常最好将它们保持私有。

如果对象是唯一控制其变量的对象,则易于使代码组织良好。

想象一下,您想保持高度在0到200之间。如果您有一个设置高度的方法,您可以轻松监视此操作。

例如(我将使用Java为例):

public void setHeight(int newHeight)
{
    if (newHeight < 0)
        height = 0;
    else if (newHeight > 200)
        height = 200;
    else
        height = newHeight
}

正如您所看到的,这种方法非常有结构和控制。

现在想象一下,我们有一行代码可以从外部编辑此高度,因为您选择将其公开。除非您在代码外部进行控制,否则您可能会得到一个与您的程序不兼容的高度。即使您想要控制它,您也将重复代码。

非常基本的例子,但我认为它能说明问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接