【问题标题】:What's the deal with Java's public fields?Java 的公共字段是怎么回事?
【发布时间】:2011-12-19 00:52:46
【问题描述】:

我一直在阅读 javaworld.com 上的两篇文章 (1)(2),关于所有类字段应该如何私有以及 getter/setter 方法同样糟糕。一个对象应该作用于它拥有的数据,而不是允许访问它。

我目前正在为Connect Four 完成一项大学作业。在设计程序时,玩游戏的代理需要访问棋盘的状态(这样他们就可以决定要移动什么)。他们还需要将此举动传递给游戏,以便将其验证为合法举动。并且在决定要移动什么的过程中,碎片被分组为带有起点和终点的威胁。

Board、Threat 和 Point 对象实际上并没有做任何事情。它们只是用来存储可以以人类可读方式访问的相关数据。

在设计之初,我将板上的点表示为两个元素 int 数组,但是在创建点或引用它们的组件时这很烦人。

所以,班级:

public class Point {
    public int x;
    public int y;
    public Point(int x, int y){
        this.x = x;
        this.y = y;
    }
}

在我能想到的每一个方面都很完美。除了它打破了我学到的每一条规则。我犯了罪吗?

【问题讨论】:

  • 我假设一个 Point 不能被改变,对于一个新的 point 你创建一个新的对象,如果是这种情况,你应该声明 xy,为 final,这个将使您的代码不那么“违法”,因为 x/y 值不会有被用户操纵的“风险”[至少不是没有反射],类似于 java 数组的长度......跨度>
  • 是的,这是一个可怕的罪过,JDK 中 Point 类的原作者、在 Android 中编写 SensorEvent 类的谷歌人员以及其他许多人也犯了此罪。没有规则是绝对的,并且没有类似结构的 getter/setter(即基本上只是一个数据存储)并不是特别值得注意。
  • “我犯了罪吗?”不会。“我会被老师打分吗?”很可能是的。
  • @WW:如果是合适的老师,如果他解释了他选择使这些字段不可变和公开的原因,他仍然会得到分数。
  • 我想补充一点,在我看来,将类字段设为私有是人们最糟糕的行为之一。如果您的课程与安全有关,那么请转至私人课程。否则不要,如果他不将那些无害的字段设为私有,我可以通过扩展课程来节省很多时间......

标签: java oop


【解决方案1】:

公共字段将对象的表示暴露给它的调用者,即如果表示必须改变,那么调用者也应该这样做。

通过封装表示,您可以强制调用者如何与之交互,并且可以更改该表示,而无需修改调用者,前提是公共 api 未更改。在任何重要的程序中,封装对于实现合理的可维护性都是必要的。但是,虽然您需要胶囊,但它们的适当粒度可能大于单个类。例如,从 Collection 的内部表示中封装 Iterator 是没有意义的。

不碍事,让我们看看你的例子:

public class Point {
    public int x;
    public int y;
    public Point(int x, int y){
        this.x = x;
        this.y = y;
    }
}

该类的内部表示极不可能更改,因此通过将字段设为私有来隐藏表示的结构没有任何好处。但是,我会阻止调用者在构造 Point 后对其进行修改:

public class Point {
    public final int x;
    public final int y;
    public Point(int x, int y){
        this.x = x;
        this.y = y;
    }
}

以便真正希望封装其状态的类可以返回其 Point 而无需 leaking 其内部表示,并在其表示中使用给定 Point 而不使用 capturing 它。这也非常适合点的数学概念,它没有身份或状态变化。

在设计程序时,玩游戏的代理需要访问棋盘的状态(以便他们决定要移动什么)。他们还需要将此举动传递给游戏,以便将其验证为合法举动。在决定要移动什么的过程中,碎片被分组为带有起点和终点的威胁。

Board、Threat 和 Point 对象实际上并没有做任何事情。它们只是用来存储可以以人类可读方式访问的相关数据。

现在这听起来像是浪费了封装的机会:代理商真的不应该被允许任意修改董事会,而应该被限制在合法的行动中。当要更新的状态位于 Board 类中时,为什么要由 Game 类决定什么是合法移动?如果Board 自己验证移动,任何呼叫者,尤其是代理,都不能违反游戏规则:

public class Board {
    // private fields with state

    // public methods to query state

    public void perform(Move move) throws IllegalMoveException;
}

【讨论】:

  • 关于董事会的精彩说明。它也使得 GooBoard 扩展棋盘、ChessBoard 扩展棋盘等变得更容易
【解决方案2】:

将字段公开并不总是一种罪过,但您会严重限制自己,因为您将实现与使用它的类耦合。稍后说您想添加一个侦听器,该侦听器在设置 X 中的值时通知您。如果不重构所有内容,您将无法做到这一点。如果您实现 setX() 并隐藏字段本身,则只需更改 setX() 的实现即可通知您进行了更改,而使用此方法的类没有更改。

【讨论】:

  • +1 for refactoring/listeners:澄清一下,在企业开发中,对象通常基于方法名称和字段(JavaBean 规范)来反映。它基本上是 Java 版本的属性——人们对它有不同的看法(他们都是对的)。对这个主题有一个有趣的看法是 project lombok,他们使用注释和创造性编译作为解决这个共同关注点的一种方式。我不建议将它用于家庭作业,但它有助于快速发展。
  • Getter 和 setter 都不错,但我认为确实可以做得过火。点是在其他类 C 语言中作为结构实现的经典示例,我敢于让任何人找到一个真实世界的示例,其中将公共字段用于类似的东西会导致问题。
  • 值得注意的是,这种重构可以由任何体面的 Java IDE 自动执行——前提是您可以访问所有调用者的源代码。
  • @Voo 这很简单;很多东西期望会有getter,尤其是反射工具——在过去的十年里,我可能遇到过十几次。
  • @DaveNewton 我应该更具体一些。我的意思是拥有一般用途的公共字段 - 另外为所有依赖它们的工具(或至少将其作为默认值,例如休眠)设置 getter/setter 就可以了。
【解决方案3】:

这是 c# 相对于 Java 的优势之一。您可以声明一个具有公共字段的类,然后改变主意使用带有 getter 和 setter 的隐藏字段;但是调用者的语法是一样的

public class Point
{
     public int X;
}

变成

public class Point
{
     int m_x;
     public int X {get {return m_x;} set {m_x = value;}
}

但调用者总是这样做

Point p;
p.X = 12;

【讨论】:

  • 在这种情况下,Point 类最好是不可变的。
  • 虽然适用于您自己的代码,但对于库来说这是不可接受的,因为属性和字段的二进制接口不同,因此这是 .dll 库的重大更改。
【解决方案4】:

如果您知道接口不会改变,那么将变量设为 public 是完全合法的。问题是随着程序变得越来越复杂,您需要更改对该字段的访问权限,但现代 IDE 使重构变得容易,所以我会说继续。

【讨论】:

    【解决方案5】:

    是和不是。

    不,如果:您确定自己的范围,并且不需要任何需要普通 Java 属性(getter/setter)的东西,那么这可能没什么大不了的。

    是的,如果:某些行为可能会改变,例如您需要抽象影响 x 或 y 的计算而不影响调用代码,或者您正在使用需要 getter 和/或 setter 的东西。

    一般来说,遵循普通的 Java 属性模式是最容易的——它消除了一种风险。这也是我希望 Java 拥有真实属性的原因之一。

    【讨论】:

      【解决方案6】:

      Java 的“规则”不是绝对的。在您的情况下,当您仅使用对象来存储数据而不提供关键行为时,将字段公开是完全可以的。

      另一方面,对于您不需要或不想向用户公开的字段,因为它们可能仅在内部上下文中相关,那么您应该将字段标记为私有并仅提供 getter 和/或 setter如果你真的需要/想要。

      您的对象越复杂,确保操作之间的一致状态就越重要,因此程序员就越有可能将对象的状态很好地封装。

      【讨论】:

        【解决方案7】:

        我认为这没有问题。如果是一种罪,它至少不是一个凡人的罪。向量是另一种方法,但从我在 java 中收集到的向量需要太多开销。

        【讨论】:

          【解决方案8】:

          公共字段打破了封装规则,即数据保护。是的,它们确实保存了数据,但是通过使用您的实例变量 PUBLIC,您的工作区中的任何类都可以访问它。 实例变量可以像私有一样受到保护。使用 getter 和 setter 方法只是为了修改类的实例变量保存的数据。 通常你的 setter 方法会有某种验证,这就是为什么我们必须保护损坏数据的实例变量(通过将它们标记为私有或受保护)

          在上面的示例中,您有一个构造函数,它正在初始化您的 inst。变量,但是作为此类的开发人员,您很有可能有权并知道要插入什么数据以保持类的完整性。在您的课程上工作的其他人可能没有意识到这一点,并且可能会通过破坏封装和整个程序来访问您的变量。

          考虑 x=-10;当 x 只能从 0 到 100 时(例如)。 我建议坚持封装原则。 希望这会有所帮助!

          【讨论】:

            【解决方案9】:

            有时规则会预测您可能没有考虑过的使用情况。

            如果您制作一组点或点的哈希图怎么办?为了让它按照你希望的方式工作,你应该实现 equals() 和 hashcode() 重载,但是最好的做法是使对象不可变 - 这样你就不能从下面更改值设置/映射。

            【讨论】:

              猜你喜欢
              • 2013-03-19
              • 1970-01-01
              • 2010-12-25
              • 2012-04-28
              • 1970-01-01
              • 1970-01-01
              • 2010-09-29
              • 2011-07-21
              • 1970-01-01
              相关资源
              最近更新 更多