【问题标题】:Clean Code: Should Objects have public properties?清洁代码:对象应该具有公共属性吗?
【发布时间】:2011-03-12 19:48:10
【问题描述】:

我正在阅读“清洁代码”一书,并且正在为一个概念而苦苦挣扎。在讨论对象和数据结构时,它声明如下:

  • 对象将其数据隐藏在抽象之后,并公开对这些数据进行操作的函数。
  • 数据结构暴露了它们的数据并且没有有意义的功能。

所以,我从中得到的是,我的对象不应该有任何公共属性,我应该只拥有对属性执行操作的方法。如果我确实需要访问属性,它们应该位于数据结构上,可以从我的对象上的方法返回?使用这种方法,我似乎需要一个 GetHeight() 和 SetHeight() 方法用于我的对象的 Height 属性,而不仅仅是使用 getset属性。

也许我不完全理解所建议的内容,但这是我对“对象隐藏其数据”的理解。如果您能帮助我理解这一点,我将不胜感激!

提前致谢!

【问题讨论】:

  • 补充以下答案,混淆可能源于许多语言不支持属性这一事实。在这种情况下,您可以在访问器方法和公共字段之间进行选择,而正确的选择始终是访问器方法。 C# 没有这个问题,因为它支持属性。

标签: c# data-structures object properties abstraction


【解决方案1】:

在纯 OO 中,“真实对象”必须完全隐藏它用来履行职责的数据。因此,无论是通过公共字段、公共属性还是公共 getter/setter 函数,都必须避免暴露内部数据。

仅通过属性路由访问,内部数据既不隐藏也不隐藏!

回答您的问题: - 如果您正在编写对象,请避免使用公共属性 - 如果您正在编写数据结构,请使用公共属性(公共字段也可以完成这项工作)

【讨论】:

    【解决方案2】:

    虽然公共属性不会立即引起代码异味,但请考虑这篇文章:

    Coding with Reason by Yechiel Kimchi(摘自97 Things Every Programmer Should Know一书)

    “...不要向对象询问要使用的信息。相反,请让对象使用它已经拥有的信息进行工作。”

    这并不总是发挥作用(例如,数据传输对象)。我要注意的是Inappropriate Intimacy

    【讨论】:

    • +1 以获取挂在我办公桌上的报价!感谢您的参考。
    • 这是严格的 OOP 方法。在某些情况下很难实现。考虑 MVVM 模式。
    • 整本书可通过 Github 免费获得。 97-things-every-x-should-know.gitbooks.io/…
    【解决方案3】:

    这是交易。

    虽然公共变量有时可能很有用,但通常最好将它们保密。

    如果对象是唯一可以控制其变量的对象,则很容易使代码保持井井有条。

    假设您希望将高度保持在 0 到 200 之间。如果您有设置高度的方法,则可以轻松监控。

    例如(为了速度,我将使用 Java):

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

    如您所见,这种方法非常结构化和可控。

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

    非常基本的例子,但我认为它明白了重点。

    【讨论】:

      【解决方案4】:

      读完《清洁代码》后,我建议你阅读 Bob Martin 的另一本书:

      Agile Principles Patterns and Practices In C#

      在本书中,本书的大部分内容都讨论了一个案例研究,其中 Bob 应用了干净代码中讨论的原则。我首先阅读 Clean Code,但回想起来,我认为应该首先阅读“Agile Patterns..”,因为 Clean Code 更像是日常手册或良好 SW 原则手册。

      例如,在“敏捷模式...”中使用了以下代码:

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

      以下使用公共数据的验证处理您的问题:

      不要因为使用 public 被冒犯 数据成员。这不是一个对象 真正的意义。它只是一个 数据的容器。它没有 有趣的行为,需要 封装。制作数据 变量私有,并提供 getter 和 setter 将是一种浪费 的时间。我本可以使用结构 而不是一堂课,但我想要 OrderData 通过引用传递 而不是按价值。


      旁白:

      就个人而言,我不得不说 Robert Martin(与 Martin Fowler、Michael Feathers 一起)通过这些书为软件开发者社区做出了巨大贡献。我认为它们是必读的。

      【讨论】:

      • 还有The Clean Coder - 不同的主题,但非常值得一读恕我直言。
      【解决方案5】:

      与此线程中的其他帖子一样,我将指出 C# 中的属性只是您提到的访问器函数的特殊情况。事实上,你可以在你的对象的 IL 中对 get_Property 和 set_Property 方法进行微调,这些方法有一个指示它们是属性的标志,对于实现 add_ 和 remove_ 前缀方法的事件也是如此。

      处理抽象时的一个重要区别是设置属性是否会作用于对象,而不仅仅是更新内部状态或引发 PropertyChanged 异常。

      如果您查看大量内部 BCL 对象,您会发现属性的实现方式是,您可以按任意顺序设置所有属性来配置对象。如果完成了任何复杂的处理,那么通常描述将要发生的事情的方法是更好的选择。

      【讨论】:

        【解决方案6】:

        它主要是术语“财产”的另一种定义。 C# 中的属性不是大多数其他语言所认为的属性。

        示例:
        C++ 公共属性是:

        class foo
        {
          public:
            int x;
        };
        

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

        class foo
        {
          public int x;
        }
        

        我们在 C# 中命名为属性的东西在其他语言中是 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# 属性完全没问题,只是不要盲目地默认它们来设置 并且不要将类中的每个数据字段都设为公共属性,想想你类的用户真正需要知道什么/改变。

        【讨论】:

          【解决方案7】:

          这本书试图描述一个对象不应该暴露类的实际实现方式的理论。在更复杂的对象中,许多内部变量不一定能从外部角度传达正确的信息,而应该只具有作用于它们的方法。

          但是,当您拥有简单的对象时,将其作为硬性规定就会失效。在矩形的情况下,高度和宽度是用户想要知道的基本属性。而且由于它的实现很简单,不使用 get 和 set 只会让你的代码比它需要的更复杂。

          【讨论】:

            【解决方案8】:

            实际上是通过使用属性,例如

            public class Temp
            {
               public int SomeValue{get;set;}
               public void SomeMethod()
               {
                 ... some work
               }
            }
            

            您正在隐藏其数据,因为存在一个隐式变量来存储由 SomeValue 属性设置和返回的值。

            如果你有

            public class Temp
            {
               private int someValue;
               public int SomeValue
               {
                 get{ return this.someValue;}
                 set{ this.someValue = value;}
               }
               public void SomeMethod()
               {
                 this.someValue++;
               }
            }
            

            那你就会明白我的意思了。您正在隐藏对象的数据 someValue 并使用 SomeValue 属性限制对它的访问。

            【讨论】:

              【解决方案9】:

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

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

              public int Height { get; set; }
              

              进入

              public int Height { get { return m_width; } set { m_width = value; } }
              

              如果您决定您的对象应该始终是方形的。使用您的类的代码不需要任何修改。

              因此,如果您的对象公开了公共属性,它仍然“将数据隐藏在抽象背后,并公开操作该数据的函数”,正如本书所建议的那样。

              【讨论】:

              • 请注意:Height/Width 违反了 Liskov 替换原则(来自 Robert M. Martin 的 Code clean)。
              • 是的,这是一个非常糟糕的例子 :)
              【解决方案10】:

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

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

              【讨论】:

                【解决方案11】:

                使用私有字段生成公共访问器会在用户代码和您的类之间建立契约。理想情况下,此合同不应更改代码的修订版。

                在 C# 中,强制遵守合同的方法是使用 interface。接口将允许您指定所需的方法和属性实现,但不允许字段。

                此外,在 .NET 的各个方面,属性通常比字段更受欢迎。例如PropertyGrid控件只枚举属性,ASP.NET MVC模型类需要属性等。

                【讨论】:

                  【解决方案12】:

                  确实,C# 属性不是数据,而是访问器,因此它是对数据进行操作的函数。

                  您应该避免使用公共字段,而不是公共属性。

                  【讨论】:

                  • 我避免使用 DTO,但在特定情况下。当必须使用它们时,我更喜欢自动属性。
                  • 对于几乎所有项目,没有理由更喜欢属性而不是字段,并且有几个理由更喜欢字段。字段是: [1] 保证无行为(更改为属性以添加行为需要重新编译,这是); [2] 有时快,从不慢; [3] 有更短的代码; [4] 可以是readonly,这是一个比get 更强的保证。仅当您编写的公共 API 的属性需要允许二进制兼容的未来版本中的行为或需要私有设置器时才使用属性(但请考虑 readonly)。
                  • 因此,当组织成员在类中的位置时,属性将与其他方法保持一致,在首先调用它们的方法之下,对吗?
                  • @toddmo 这只是格式问题,而不是设计问题。无论如何,我将它们内联
                  【解决方案13】:

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

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2012-02-14
                    • 2021-04-17
                    • 1970-01-01
                    • 1970-01-01
                    • 2010-10-31
                    • 1970-01-01
                    • 2015-08-25
                    • 2021-07-03
                    相关资源
                    最近更新 更多