【问题标题】:When does it make sense to use a public field?什么时候使用公共字段才有意义?
【发布时间】:2011-10-22 19:54:30
【问题描述】:

这是我已经有一段时间的问题了:

像这样公开公开字段什么时候有意义?

public class SomeClass()
{
   public int backing;
}

这样做的缺点(除了激怒 OOP 精英之外)是,如果您需要在此数据之上添加任何逻辑,则必须对 API 进行重大更改。我想这就是精英们所谈论的。

长期以来,Java 和 C# 的最佳实践是使用 getter/setter 或属性来访问字段。

public class SomeClass()
{
   private int backing;

   public int getBacking()
   {
      return backing;
   }

   public void setBacking(int v)
   {
      backing = v;
   }
}

C# 已将其演变为具有自动属性的非常简单的语法:

public class SomeClass()
{
   public int Backing { get; set; }
}

懒惰的我仍然觉得这太长了,因为我发现自己做了很多事情。更重要的是,我不确定我知道公共领域在哪里更有意义。

为什么不将公开声明的字段视为幕后的属性(或方法)?这样就不可能激怒脱钩之神,也不会少打字。

public class SomeClass()
{
   public int backing;   // The same as public int backing { get; set; }
}

对于只包装底层字段的属性,我很确定 JIT 优化了方法调用,因此性能可能不是问题。有什么想法吗? (除了字段名称的正确大小写约定)

编辑:感谢所有回复。我觉得也许不是每个人都理解我的问题。什么时候公共领域比财产更好?为什么是更好的选择?如果唯一的原因是方便(更少的打字和混乱),那么每当遇到公共字段时让编译器生成一个“幕后”属性会有什么缺点。基本上,创建一个真正的公共字段是不可能的(因为它们都是属性)。那会有什么问题呢?谢谢。

【问题讨论】:

  • 如果您不想输入整个属性声明,您可以使用属性 sn-p ("prop")
  • 如果您使用的是 Visual Studio,请将光标放在您想要属性的位置并键入“prop”,然后按两次 TAB。将生成一个属性模板并突出显示该类型。输入属性的类型,然后按 Tab。输入属性的名称,然后按 Enter。完毕! msdn.microsoft.com/en-us/library/f7d3wz0k(v=VS.90).aspx

标签: c# java oop design-patterns


【解决方案1】:

在所有其他答案中,在某些情况下您必须在公共类中使用公共字段。例如使用 Unity 3D。序列化程序只序列化字段而不是属性(以获得更好的性能)。因此,在实现您的课程之前,请研究您正在使用的库和框架是如何工作的。他们可能需要公开您的某些字段才能按预期工作。

【讨论】:

    【解决方案2】:

    在非常复杂的项目中,很少(但有时)使用公共字段是个好主意。想到的一个例子:

    /** 
     * A two-dimensional mathematical vector. Immutable so instances may be freely shared
     * without violating encapsulation.
     */
    public class Vec2 {
        public final int x, y;
    
        // bunch of constructors and methods omitted
    }
    

    基本原理:在读取xy 时,极不可能需要更改内部表示或执行任何类型的操作。也就是说,使用 setter 在这里没有任何好处。

    但它会带来一些成本:

    1. public final 字段将清楚地表明该类是不可变的,使用 getter,您必须相信文档注释。 (请注意,缺少 setter 并不意味着不变性,因为其他一些方法可能会分配该字段)
    2. 访问向量的代码可读性稍差,因为 get() 没有提供有意义的语义信息,但在阅读代码时仍需跳过(至少在 Java 中,C# 更好)。
    3. 这是一个极低级别的类,经常使用是可能的,调用开销可能会成为一个问题。是的,虚拟机在很多情况下都可以执行内联,但至少对于非最终方法,即使当前没有加载子类,也可能会保留一些开销。

    【讨论】:

    • 在我看来,具有公共字段的类型宣传其目的是将一些值与胶带粘在一起。这样的类型并不是“笨拙的”,但如果需要将一些值与胶带粘在一起,最好定义一种类型来执行此操作,而不是尝试“像对象一样行动”。具有公开不可变字段的类、具有公开可变字段的类和(在 .NET 中)具有公开可变字段的结构都有其自身的有用语义(可变公开字段类很少见,但可能是合适的,尤其是在 Java 中,在情况...
    • ...对方法的多次连续调用将需要向调用者返回多个值。让调用者将一个可变对象传递给被调用的方法,该方法可以将所需的值存储在其中,这通常可以避免为每次调用分配一个新对象。
    【解决方案3】:

    长期以来,Java 和 C# 的最佳实践是使用 getter/setter 或属性来访问字段。

    我认为这是第一个问题:如果你有一个 getter,你也必须有一个 setter。这是个错误;在许多情况下,setter 是不必要的,因为没有真正需要修改对象的属性值,并且消除这种可能性会简化类。

    极端情况是根本没有突变器。使类不可变 的最后阶段是永远不要使可变字段值(请记住,Java final 不像 C++ const)可直接访问。小值类应该是不可变的。只有 getter 方法可以通过创建副本来提供对潜在可变字段的安全访问(在 C++ 中,您只需拥有一个按值返回的 getter)。对于那些不需要防御性复制的字段,您可以免除 getter,但是类中的某些字段可能需要 getter,而有些则不需要。但这会令人困惑,

    【讨论】:

    • 谢谢。这是一个很棒的想法。公共字段当然也有同样的问题,所以让它成为一个属性(编译器生成的 getter/setter)不会让事情变得更糟。此外,我不建议取消手动创建与“私有”字段一起使用的 getter/setter 代码的能力。因此,除非我遗漏了什么,否则这不是将公共字段保留为语言功能的理由(与语法上看起来像公共字段的属性相反)。
    【解决方案4】:

    以下是对公共领域优缺点的总结:

    优势

    • 代码更简洁
    • 更好的性能(在某些情况下)

    缺点

    • 不更改 API 就无法更改表示
    • 不能强制执行不变量(除非该字段是不可变的)
    • 访问这些字段时无法执行其他操作

    那么我们什么时候应该使用公共字段呢?要看。很明显,对于公共课程来说,劣势大于优势。开发包内的代码可能出现的问题远比代码中的更混乱和对性能的轻微影响要糟糕得多。

    但是,一个类可以是包私有的,甚至是私有嵌套的,在这种情况下,对代码的可能更改将被本地化。因此,绝对可以使用公共字段。也有性能差异不是那么小的情况。例如,Android 开发者指南声称,在可能的情况下使用 direct field access instead of getters/setters 是一种很好的做法,因为它的速度要快几倍。

    总结一下,我将引用我最喜欢的一本书,J. Bloch 的《Effective Java》,第 14 条:

    总之,公共类不应该公开可变字段。公共类公开不可变字段的危害较小,尽管仍然值得怀疑。然而,有时需要包私有或私有嵌套类公开字段,无论是可变的还是不可变的。

    【讨论】:

    • 你有什么“更好的性能”的来源吗?我总是认为没有任何逻辑(甚至只有少量逻辑)的属性会被内联。
    • @aero 链接就在答案中,我在其中提到了 Android 开发人员指南。我同意在现代虚拟机中差异很可能可以忽略不计。然而,谷歌声称,在 Android 上,没有 JIT 的差异是 3 倍,而在 Android 2.2 上,有 JIT 的差异甚至是 7 倍。因此,当您必须考虑性能时,至少存在这种情况。
    • @Malcolm - 非常有趣的是,Dalvik 在这种情况下没有内联调用。至少,这警告不要对虚拟机可能做什么或不做什么做太多假设。这让我想起了一些关于服务器 JVM 与客户端的行为方式的 Scala 交叉对话。谢谢你的链接。
    【解决方案5】:

    公共字段是更好选择的一种情况是提供类的常量。

    例如,参见:

    public const double PI
    

    在 System.Math 中定义。

    这种方法很受欢迎,因为它明确告知您的类的消费者,该成员不包含任何逻辑、验证或任何其他与状态相关的操作,因此可以在您想要的任何上下文中使用。

    我能想到的另一种情况是,当您需要将类作为容器进行简单操作(或仅用于将大量参数传递给方法)时,例如参见System.Windows.Point。但在大多数情况下,这些容器被建模为结构体。

    【讨论】:

    • “它明确地通知你的类的消费者,这个成员不包括任何逻辑、验证或任何其他与状态相关的操作,”这对我来说是一个重要的概念。出于这个原因,我经常使用公共只读字段。虽然在大多数情况下与常量并不完全相同。出于某种原因,fxcop 一直在抱怨这个建议属性,而不是:/.
    • 是的,这个不错。我同意不应将“const”或“readonly”字段视为属性。
    【解决方案6】:

    在我看来,当你设计一个类的结构时,你应该更多地关注未来的变化,并且应该始终对它们友好。如果未来的需求需要您在返回值之前执行一些逻辑,而不仅仅是返回字段的值,则您必须更改类的接口,并且您的库的所有用户都必须进行更改。这通常会变成一场灾难。

    请记住,open / close principle

    【讨论】:

    • 我的观点完全正确。我建议的是以后界面不要改变。即使您键入看起来像简单的公共字段的内容,编译器也可以将其视为引擎盖下的属性。然后,当你意识到你需要一个属性时,接口就不需要改变了。
    • 值得注意的是,这个参数假设你的代码是由你无法控制的代码调用的,即你正在编写一个库。否则,将字段转换为属性很简单,只需在 IDE 中选择 Refactor -> Encapsulate Field。
    【解决方案7】:

    至少在 .NET 中,一个场景是与 C API 互操作。您经常会声明 C# 或 VB 版本的 Windows API 结构,并且通常为这些使用公共字段,因为将字段设为私有的正常原因——以防止有人在你背后弄乱它们——会失效。在这种情况下,您知道某些东西将会改变您背后的字段——这就是拥有该结构的全部目的!

    当然,您通常不会将那些未封装的结构暴露给应用程序代码 - 您会将它们视为 P/Invoke 模块的私有元素,因此应用程序代码仍然不会处理公共字段。

    【讨论】:

    • 谢谢。在这种情况下是否有理由公开这些字段,还是只是惯例?当我做类似的事情时,我主要是写到私人领域。
    • 从功能上讲,没有理由将字段设为私有或公开:Win32 只看到内存中的布局,可见性不会影响它。所以我想这只是惯例。将字段设为私有并没有封装好处,并且使用包装器属性加载结构不会强调其“普通 C 样式结构”的性质,并使其看起来像一个真正的业务对象。或许这只是我的眼睛咧嘴笑
    【解决方案8】:

    什么时候公开公开字段有意义?

    在其他地方,私人课程如下。

    public class MyOuterClass
    {
          private class MyInnerClass
          {
                public int val ; // why bother w/boiler plate
          }
    }
    

    【讨论】:

    • 谢谢。我的问题是为什么编译器不为我生成样板?我想准确地写出您所做的事情,但也具有财产的未来灵活性。在我开始构建一个破碎的虚荣语言之前,我希望了解它的缺点。
    【解决方案9】:

    我认为当您想要对一些变量/对象进行分组时(有点像 C struct)是有意义的。例如:

    class Pixel {
      public int x;
      public int y;
      Color c;
      // Other Pixel related information
    }
    

    由于没有方法,所以如果使用了错误的值,不会有任何中断,并且它很好地将这些变量放在一起。

    【讨论】:

    • 直到有一天您决定 x 和 y 都必须是正数,或者您尝试在“c”上合并颜色匹配或其他转换作为实现细节时,什么都不会中断。 :-) 我同意,尽管我们编写的大量代码永远不会到来。我希望您的示例具有视觉简洁性和吸引力,并具有内置属性的灵活性。
    • 如果您决定更改数据的“规则”,当然您还需要更新(部分)应用程序的其余部分,无论是否使用公共字段。
    • 我不这么认为。如果您的示例中的“x”已经是引擎盖下的属性,那么稍后使用显式 get/set 方法扩展它不会更改接口。 'x' 仍然只是一个属性。所以,这就是我认为的优势。缺点是什么?我可能遗漏了什么。
    • @Justin: 如果接受Pixel 的方法要求 x 和 y 必须为正数,则该方法必须验证它们,或者必须使用不同的数据类型 i> 为此目的。
    【解决方案10】:

    您可以在 java 中使用公共字段,然后向其中添加一些逻辑,例如是否有 getter,使用 AspectJ,包括避免写入它,如果不是来自特定 psckage 等...

    虽然这是一项重大改进,它加快了编码速度,并且仍然为未来的改进留出了很大的空间,而无需每个人都重写所有代码,但它的使用并不多,因为精英主义者是出了名的偏执狂。

    回到您的问题,由于 jit 与 getter/setter 相比,使用公共字段并没有太大的改进,而且由于 IDE 具有自动生成 getter 和 setter 的功能,因此在使用 getter 和 setter 时没有真正的程序员开销。在多年的 Java 编程中,我只使用过几次公共字段,并且总是最终将它们转换为 getter/setter 对。

    【讨论】:

    • 我对 AspectJ 不是很熟悉,但您说的是在运行时注入字节码对吗?这完全是另外一回事。谢谢。
    • 是的,它是关于注入字节码的。虽然这对大多数人来说听起来很可怕,但任何 ORM 都在这样做:)
    【解决方案11】:

    所以我能给出的最佳答案是,当你通过反射来做魔法时,就像在利用静态类型语言时通常会做的那样,属性比字段更有意义。从 ORM 工具到映射器,再到绑定数据,从行为的角度来看,属性可能与字段具有不同的含义。

    JITter 不会将属性转换为字段,它可以将它们内联,但我不会保证在所有情况下都会这样做。

    【讨论】:

      猜你喜欢
      • 2013-10-07
      • 1970-01-01
      • 2014-06-06
      • 1970-01-01
      • 2014-07-21
      • 1970-01-01
      • 1970-01-01
      • 2013-03-05
      • 1970-01-01
      相关资源
      最近更新 更多