Java接口使用指南--接口中的getter和setter方法是否不好?

42

人们如何看待在界面中使用的最佳指南?应该将什么放入界面,不应该放入界面?

我听说过一般规则是,一个接口必须仅定义行为而不是状态。这是否意味着一个接口不应该包含getter和setter?

我的观点:也许对于setter不是这样,但有时我认为getter是有效的放置在接口中。这仅仅是为了强制实现类实现那些getter,并表示客户端可以调用那些getter来检查某些东西。

11个回答

28
我不明白为什么接口不能定义getter和setter。例如,List.size()实际上是一个getter。接口必须定义行为而不是实现 - 它不能说你会如何处理状态,但它可以坚持你可以获取它并设置它。
例如,集合接口都涉及状态 - 但是不同的集合可以以根本不同的方式存储该状态。
编辑:评论表明getter和setter意味着使用简单字段进行后备存储。我强烈反对这种暗示。在我看来,这意味着“相对便宜”地获取/设置值,但并不意味着它作为具有微不足道实现的字段存储。

编辑:如评论中所述,这在JavaBeans规范第7.1节中已经明确说明:

因此,即使脚本编写者键入 b.Label = foo 仍然存在一种方法调用到目标对象以设置属性,而且 目标对象具有完全的编程控制权。

因此,属性不仅可以是简单的数据字段,实际上它们可以是计算值。更新可能会有各种编程副作用。例如,更改bean的背景颜色属性可能还会导致bean使用新颜色重新绘制。"


如果所谓的暗示是正确的,我们可以直接公开属性作为字段。幸运的是,这个暗示是不成立的:获取器和设置器完全有权计算东西。
例如,考虑一个组件,其中包含
getWidth()
getHeight()
getSize()

你是否认为这里有三个变量的暗示?不是将其简化为:

private int width;
private int height;

public int getWidth() {
    return width;
}

public int getHeight() {
    return height;
}

public Size getSize() {
    return new Size(width, height); // Assuming an immutable Size type
}

或者(个人认为最好的方式):
private Size size;

public int getWidth() {
    return size.getWidth();
}

public int getHeight() {
    return size.getHeight();
}

public Size getSize() {
    return size;
}

这里的size属性或者height/width属性仅仅是为了方便而存在,但我并不认为这使它们无效。


16
我不同意那个暗示。如果Getter需要的话,完全可以计算出属性的值。 - Jon Skeet
3
否则你最好公开该字段并完成它。将其作为方法的整个目的是隐藏实现,而不是改变它。 - Jon Skeet
5
我认为做出这种假设是玩火,因为Java API中有很多东西,也有很好的理由。我的意思是,如果你调用一个方法而不是直接访问字段,那么暗示是你不应该假设它什么都不会做。如果我真的想传达只需要访问一个字段,我会暴露一个字段。 - Jon Skeet
2
如果你假设getWhatever()只是返回字段“whatever”的值,那么总有一天(很快)会有事情证明你错了。 - Brian Agnew
3
“Part of the persistent state”不等同于“该类型的字段”。持久状态可以保存在映射中,也可以作为某个更大类型的一部分(就像我展示的宽度/高度/大小的例子中那样)。 - Jon Skeet
显示剩余9条评论

14

我认为一般有两种类型的接口:

  1. 服务描述。例如 CalculationService。我认为不应该在这种类型的接口中包含 getX 这样的方法,当然更不应该包含 setX。它们明显暗示了实现细节,而这并不是这种类型接口的职责。
  2. 数据模型 - 仅存在于系统中抽象出数据对象的实现。这些可能用于测试或只是因为像我这样年纪大的人还记得(例如)使用持久化框架将你束缚在一个特定的超类中(也就是说,如果你切换持久层,则会选择实现接口)。我认为,在这种类型的接口中具有 JavaBean 方法是完全合理的。

注意:集合类可能适合于第二种类型。


那非常接近过程式编程了。 - Tom Hawtin - tackline
我无法确定你是在开玩笑 :-/ 如果不是,那么什么东西非常接近 PP?而且,无论如何,PP有什么问题吗? - oxbow_lakes
4
我猜你的意思是让我将数据与行为分离。这种风格或许有点老式,但是10年前我用Smalltalk写了一个“库”程序,采用了这种“对象生命力旺盛的社区”的风格,唯一让我印象深刻的是我永远想不起我的业务逻辑存放在哪里。书是借给顾客的吗?还是顾客查阅书籍? - oxbow_lakes
4
在您上述的情景中,您不需要一个图书管理员吗? - Brian Agnew
4
哈哈,是的。那正是我所需要的——对某些中央“管理者”的逻辑,而不是数据! - oxbow_lakes

12

有关getter/setter方法并没有本质上的邪恶,但是:

  1. 首先,我倾向于将对象在字段方面变为不可改变的。为什么?因为我在构造阶段实例化了大部分东西。如果以后要更改某些内容,则可以放宽这些限制。因此,我的接口通常包含getter方法,但不包含setter方法(还有其他好处-特别是线程)。
  2. 我希望我的对象为我完成任务,而不是相反。因此,当我的对象获得多个getter方法时,我开始考虑该对象是否应该具有更多的功能,而不是暴露其所有数据以供其他东西使用。有关详细信息,请参见此答案

这些只是指南,注意。


+1 提到了线程。我写了一个带有getter和setter的服务,现在不得不重新思考我的设计,因为它不是线程安全的。 - Jake88

6

一般情况下,我认为一个bean不应该有一个接口。从更一般的意义上说,javabean本身就是一个接口。接口指定了更复杂实体的外部契约,而javabean的外部契约和内部表示是相同的。

然而,我不认为接口中不能有getter方法。对于一个可读取数据的东西,例如ReadableDataThingie接口,它被DataThingieBean实现是完全合理的。


2
我听人们说,通常情况下,一个接口只应该定义行为而不是状态。这是否意味着接口不应包含getter和setter?
首先,在Java中,排除异常声明,你无法定义完整的行为而没有状态。在Java中,接口不定义行为。它们不能。它们定义的是类型;实现一组特征签名的承诺,可能涉及一些关于异常后置条件的许诺。但仅此而已。行为和状态由实现这些接口的类定义。
其次,如果在接口中定义getter和setter,它们实际上并未定义完整的行为(除了一个是针对属性的读取,一个是针对属性的写入)。你可以在setter和getter后面定义复杂的行为,但它们只能在实际的类中实现。在Java语言中,除了最限制的情况外,没有任何东西允许我们自由地在接口中定义行为。
考虑到这一点,从语法和语义上看,在接口中具有setter和getter是没有问题的。
如果你的应用程序被很好地建模,并且问题需要你定义具有setter和getter的接口,那么为什么不呢?例如,看一下ServletResponse接口。
现在,如果我们从实现符合JavaBeans规范的类的角度来看getter和setter,那么你不需要为它们定义接口。
但是,如果你有需要setter和getter的东西(就像一个bean可能需要的那样),并且编译时需要被插入(而不是在运行时像bean那样),并且可能存在多个实现,那么是的,这需要定义定义getter和setter的接口。
希望这可以帮到你。

1

这涉及到整个Getter/Setters是邪恶的话题,这个话题在本网站和其他地方都得到了多次解决。

我倾向于不在接口中使用访问器,而是使用构造函数参数来添加协作者到实现中。


1

如果某个东西的直接实现方式是作为getter,那并不意味着它不能在接口中使用。


1
我认为实现细节并不决定某个东西是否在接口中。相反,客户端对象是否需要它来完成工作?如果不需要,那么它可能不应该存在。 - Brian Agnew

1

我曾经使用过这种接口,例如我们有带有beginDate和endDate字段的类。这些字段在许多类中都存在,而我需要为不同的对象获取这些日期,因此我提取了接口并感到非常高兴 :)


0

更多阅读:实用API设计Java框架架构师的自白(Jaroslav Tulach,2008年,Apress出版社)。


0

基本上,如果回答“我需要知道[state、property、whateverThignAMaGit]的值才能使用它的实例吗?”是肯定的...那么访问器应该属于接口。

John上面提到的List.size()是需要在接口中定义的完美示例。


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