何时需要使用公共实例变量?

3
我昨天在编写自己的LinkedList实现。我做的事情类似于这样:
public class Node
{
    Node next;
    int value;

    public Node()
    {
       next = null
       value = 0;
    }
}

当我使用Node时,我在执行node.nextnode.value。然后我意识到-等等,我在做什么?这些实例变量应该是private的,然后我应该用getter()和setter()来访问,例如getNext()setValue()
这使我想知道,什么时候会希望使用除了private实例变量之外的内容?

这个回答:https://dev59.com/xmoy5IYBdhLWcg3wkO2g#8466886 给出了一些关于使用getter和setter的标准(理想?)行为的例外细节。虽然我认为它主要是指类内部访问的选择,而在那里我想你仍然有选择的余地。 - BlackVegetable
请称呼我老派,但我根本看不出使用getter或setter的优势,除非您想触发一些副作用或进行一些验证。这只是额外的、不必要的长度和括号。 - Kevin
1
但是使用空的gettersetter的一个优点是,如果将来需要添加验证逻辑或副作用,那么您只需在一个地方(即getter/setter中)编写该代码,所有已经通过getter/setter访问变量的其他代码现在都使用该验证逻辑。 - nhgrif
当你相信自己 - arynaq
@VGR 这将归入我提到的“验证”类别。我想我最近一直在Objective C中工作,所以有些宠坏了,其中(1)调用空指针不会产生任何效果(无操作/返回null/0),(2)如果您想添加getter/setter,只需正确命名即可,无需进行外部更改。 - Kevin
显示剩余3条评论
4个回答

4
技术上来说,你提出了两个问题:标题问题,“何时适合使用公共实例字段”,以及你最后一句话,“何时适合使用非私有实例字段”。
何时适合使用公共实例字段?几乎每个人都知道答案几乎从不。我认为它们偶尔可以适用于非常小的值类,这些类的行为类似于简单的C结构体。一个很好的例子是java.awt.Point和java.awt.Dimension。
事实上,这些都是特别好的例子,因为它们最终演变成使用访问器方法,尽管它们很简单,这是一个很好的教训:即使一个看起来如此简单,以至于公共字段可能似乎可接受的类也有可能成为需要封装的东西。(Point和Dimension仍然保留这些公共字段以实现向后兼容性。)
何时适合使用非私有实例字段?大多数人没有利用没有访问修饰符的字段和方法(以及类)是包私有的这一事实,但包私有字段在许多情况下都很有用。由于它们只能被同一包中的代码访问,通常情况下是您的代码,您可以相信自己不会做会破坏对象的“坏事”。我认为有相当多的合法用途可以使用包私有字段,而不是公共字段。
话虽如此,Point/Dimension规则仍然适用:有很多次我以为我可以使用包私有字段,直到后来意识到我需要通过get和set方法进行封装。
受保护的字段可能比它们应该使用得更多。在我看来,让子类直接修改状态而不是通过方法进行修改很少有大的收益。

1
当您实际上不需要保护它们免受任意访问或更改时(例如当允许该值采用任何整数或简单的空映射“检索到的值”到“对象中的值”时),在其周围包装getter或setter就没有多大意义。
如果情况是这样的,代码有时看起来更加自然,使用以下方式:
object.value = 42;

而不是:

object.setValue (42);

尽管在我看来差异很小,但我仍然更喜欢使用getter和setter。

即使在将来某个时刻您需要将逻辑纳入成员变量中,将公共成员变量更改为getter/setter可能会破坏该类的API,涉及到所有使用它的客户端的更改。

这可能包括(但不限于):

  • 限制可以设置成员变量的值。例如,将对象的年龄限制在0到137亿年之间:-)
  • 提取被导出的值,例如从摄氏度成员计算华氏温度。
  • 在设置值时具有副作用,例如更改涉及预加载一些文件到缓存中的文件名。

1
经典示例是用于表示二维数学点的类。
public class Point {
    public float x;
    public float y;

    public Point( float x, float y ) {
        x = x;
        y = y;
    }

    public void scale( float c ) {
        x *= c;
        y *= c;
    }

    public void rotate( float angle, Point center ) {
       // whatever this logic is; I forget
    }   
}

一个Point实际上只是一个包含两个可以任意变化的浮点数的容器。通过直接访问xy而不是强制用户通过getter和setter来访问,不会对任何东西造成伤害。同时,一些操作(如缩放和旋转)非常常见,值得创建一个类来提供必要的坐标数学运算方法。

请原谅(或更正)代码中可能存在的语法或其他错误。我已经很久没有写过Java了。


我可以原谅很多事情,但是我无法容忍那种混乱的数学思维,比如在没有参考点的情况下通过旋转零维点来达到某个角度。 :-) - paxdiablo
好的,那真是相当明显啊。 - chepner

0

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