【问题标题】:Reasoning behind not using non-implemented Interfaces to hold constants?不使用未实现的接口来保存常量的原因是什么?
【发布时间】:2011-09-12 14:38:49
【问题描述】:

在他的书Effective Java 中,Joshua Bloch 建议不要使用接口来保存常量,

常量接口模式是对接口的不良使用。一个类在内部使用一些常量是一个实现细节。实现一个常量接口会导致这个实现细节泄漏到类的导出 API 中。类实现一个常量接口对类的用户来说无关紧要。事实上,它甚至可能使他们感到困惑。更糟糕的是,它代表了一种承诺:如果在未来的版本中修改了类以使其不再需要使用常量,它仍然必须实现接口以确保二进制兼容性。如果一个非final类实现了一个常量接口,那么它的所有子类的命名空间都会被接口中的常量污染。

他的推理对我来说是有道理的,它似乎是每当提出问题时的主流逻辑,但它忽略了在接口中存储常量然后不实现它们。

例如,

public interface SomeInterface {
    public static final String FOO = "example";
}

public class SomeOtherClass {
    //notice that this class does not implement anything
    public void foo() {
        thisIsJustAnExample("Designed to be short", SomeInteface.FOO);
    }
}

我与一直使用这种方法的人一起工作。我倾向于使用带有私有构造函数的类来保存我的常量,但我已经开始以这种方式使用接口来保持我们的代码风格一致。 是否有任何理由不按照我上面概述的方式使用接口?

本质上,它是一种简写方式,可以防止您必须将类设为私有,因为无法初始化接口。

【问题讨论】:

  • 您有十多个未接受的问题。 ;)
  • 简单地重构掉常量interface类是不可能的吗?将常量移动到使用它们的类;将常用的移入enums。
  • @Peter Lawery,我可以为他们中的大多数人辩护。有时我会问一个问题并在尝试任何解决方案之前转向不同的东西,这对回答者来说是肯定的,但我赞成但不将任何东西标记为我没有亲自测试过的答案。有时我只是没有得到任何有用的答案,也没有时间回来写解决方案,所以问题保持开放。当我有空闲时间时,我将不得不尝试做一些簿记,但是最近没有时间或兴趣。
  • 不确定你是否会给回答你问题的人留下深刻印象。 ;) 人们应该觉得你会提出可以合理回答的问题,并且你会跟进他们的贡献。
  • 这是一个最佳实践问题,因此没有具体答案。我现在标记的是最同意的一个,但是其他答案也提出了很多其他好的观点。

标签: java interface constants


【解决方案1】:

我不认为带有私有构造函数的类比使用接口更好。

引用的内容是使用implements ConstantInterface 不是最佳实践,因为此接口成为 API 的一部分。

但是,您可以使用静态导入或接口中的值的限定名称(如 SomeInteface.FOO)来避免此问题。

【讨论】:

  • 我不同意你的观点。它仍然成为 API 的一部分。我仍然可以实现 SomeInterface。我仍然可以编写接受 SomeInterface 作为参数的公共方法。我们只是相信每个人都会坚持没有人实施 SomeInterface 的协议吗?这只是乞求开发人员缩短代码质量并降低代码质量。至少具有无参数构造函数的类对于它的意图是稍微清楚一点。它是一个没有行为的类,只有数据。接口无法传达这一点,因为它们一开始就没有行为。不清楚接口只是一个常量桶。
  • 您必须考虑您的用户和您拥有的系统的关键问题是什么,以及如何防止它们。如果这是您的应用程序遇到的最严重的问题之一,请使用代码分析工具找出发生这种情况的位置,因为您很幸运能够在这样的系统上工作。 ;) 很少有甚至了解这个问题的用户不会介意将其作为一个问题提出。
  • 用户不是重点。我们正在谈论代码质量。有很多不可扩展的、被破解的、用户非常满意的怪物(Windows?)。作为开发人员,我们的工作是关心内部结构并着眼于更深层次的问题,而不仅仅是用户体验。用户并不关心这个问题,但当他得到一个与草率、笨拙的 API 直接相关的“错误:联系系统管理员对话框”时,他确实关心。想想看,用户并不关心我们是否有有意义的日志记录或快速失败的场景以防止损坏。这并不意味着我们不这样做
  • 有很多代码分析工具可以帮助阻止您继承像ConstantsInterface 这样的接口。您在程序中担心很多,如果您担心理论上可能存在的所有小细节给用户带来问题,你会错过一个重要的细节,这会导致一个程序。
  • 它是关于编写好的、紧凑的代码的人。使用这些过时、不明智做法的垃圾输入/垃圾输出软件总是更容易出现问题。我们都承认使用接口存在问题,所以一个更好的问题是使用常量接口有什么优势使其如此吸引人?如果没有,那么当您获得一些结果并增加麻烦的风险时,为什么要这样做?
【解决方案2】:

我想它可以完成这项工作,但正如一位朋友曾经说过的那样:“你可以尝试用章鱼擦地板;它可能会完成工作,但它不是正确的工具”。

存在接口来指定契约,然后由类实现。当我看到一个接口时,我假设那里有一些实现它的类。所以我倾向于说这是滥用接口而不是使用接口的一个例子,仅仅是因为我不认为这就是接口应该被使用的方式.

我想我不明白为什么这些值首先是公开的,如果它们只是在课堂上私下使用。为什么不把他们搬到课堂上呢?现在,如果这些值将被一堆类使用,那么为什么不创建一个enum?我见过的另一种模式是只保存公共常量的类。这类似于您描述的模式。但是,可以将类设为 final 以使其无法扩展;没有什么可以阻止开发人员实现您的界面。在这些情况下,我只是倾向于使用enum

更新

这将是对评论的回应,但后来变得很长。创建一个只保存一个值的接口更加浪费! :) 您应该为此使用私有常量。虽然将不相关的值放入单个枚举中是不好的,但您可以将它们分组到单独的枚举中,或者简单地为类使用私有常量。

另外,如果所有这些类似乎都共享这些不相关的常量(但在类的上下文中是有意义的),为什么不创建一个抽象类,将这些常量定义为protected?然后你所要做的就是扩展这个类,你的派生类就可以访问这些常量。

【讨论】:

  • “我不明白为什么这些值首先是公开的,如果它们只是在课堂上私下使用”。我这样做只是为了创建一个简洁的示例。
  • @James Ah,一个人为的例子 - 明白了:)
  • 当值连接时枚举对我来说是有意义的,但是创建一个枚举来存储一个值似乎很浪费,并且放置不相关的枚举似乎是对枚举的滥用。
  • @James McMahon 然后创建一个只保存一个值的接口更加浪费! :) 您应该为此使用私有常量。虽然将不相关的值放入单个枚举是不好的,但您可以将它们分组到单独的枚举中,或者简单地为类使用私有常量。
【解决方案3】:

无论如何,常量都是一件坏事。在一个位置填充一堆字符串表明您的应用程序从一开始就存在设计问题。它不是面向对象的,并且(尤其是对于字符串常量)可能会导致开发脆弱的 API

如果一个类需要一些静态值,那么它们应该是该类的本地值。如果更多类需要访问这些值,则应将它们提升为枚举并对其进行建模。如果您真的坚持要拥有一个充满常量的类,那么您将创建一个具有私有无参数构造函数的最终类。通过这种方法,您至少可以确保降压止步于此。不允许实例化,您只能以静态方式访问状态。

这种特殊的反模式有一个严重的问题。没有任何机制可以阻止某人使用实现了这个 rouge 常量接口的类。它实际上是关于解决 java 的限制,它允许你做一些无意义的事情。

最终的结果是它降低了应用程序设计的意义,因为没有掌握语言的原则。当我使用常量接口继承代码时,我会立即猜测所有内容,因为谁知道我还会发现哪些其他有趣的 hack。

【讨论】:

  • 我基本同意你的观点,但你断言一堆东西不好,但没有真正解释原因。此外,您需要在类中为常量使用私有构造函数,以防止默认的无参数构造函数生效。
  • @bernance - 这就是我对构造函数的意思。感谢您的反馈
  • @bernance - 这是一个难以表达的话题。因为现实情况是它可能永远不会在任何人的代码中造成严重的问题,因为仍然有更好的方法来处理永远不会改变的值。我认为“用章鱼擦地板”绝对是最接近于将你的想法变成文字的人。
【解决方案4】:

为常量创建一个单独的类似乎很愚蠢。这比创建一个枚举更费力,唯一的原因就是将 unrelated 常量保存在一个地方,只是因为它们都碰巧被相同的代码块引用。希望当您考虑将一堆不相关的东西放在一起并将其称为类时,您的恶臭警报会响起。

至于接口,只要你不实现接口就不是世界末日(例如,JDK 有许多实现SwingConstants 的类),但可能有更好的方法,具体取决于什么正是你在做的。

  • 您可以使用enums 将相关的常量组合在一起,甚至可以为它们添加方法
  • 您可以将Resource Bundles 用于 UI 文本
  • 使用通过Collections.unmodifiableMap 传递的Map<String,String> 来满足更一般的需求
  • 您还可以使用 java.util.Properties 从文件中读取常量并将其包装或子类化以防止更改

此外,对于静态导入,懒人没有理由实现一个接口来获取其常量,而您可以通过使用 import static SomeInterface.*; 来实现懒惰。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多