首页 > 解决方案 > 在枚举内部提供一个被认为是“干净”的函数吗?

问题描述

我在一个源代码中发现了一些代码,它基本上使用 switch case 来遍历枚举的每个可能值并调用适当的函数,该函数根据数据类型返回一个 Number 对象。

这是一个片段:

case TYPE_16BIT_SIGNED_BE:
    measurement = response.getRegisters().getShort(0);
    break;
case TYPE_16BIT_UNSIGNED_BE:
    measurement = response.getRegisters().getUnsignedShort(0);
    break;
case TYPE_16BIT_SIGNED_LE:
    measurement = response.getRegisters().getShortLE(0);
    break;

现在我的问题是,将其添加到枚举本身被认为是好还是坏的做法?

这是我的意思的一个例子:

public enum SomethingType {
    INT((b) -> {
        return b.getInt(0);
    }),
    DOUBLE((b) -> {
        return b.getDouble(0);
    }),
    LONG((b) -> {
        return b.getLong(0);
    });

    private Function<ByteBuf, Number> getNumber;

    SomethingType(Function<ByteBuf, Number> getNumber) {
        this.getNumber = getNumber;
    }
}

标签: javafunctionenums

解决方案


人们可以发现这种方法在各种 地方都得到了推广,一个可能有据可查的例子是状态机。枚举成员也被后来在Scala 语言中采用。因此,它显然不是 Java 的怪异特性,而是一种被证明有用的理想特性。我个人在生产代码中一次又一次地使用它,特别是当需要一些静态映射时,从枚举到值或从枚举到函数。它允许结构紧凑、简洁的代码。

此外,IMO 已经令人信服地表明,如果我可以从@johannes-kuhn 提供的链接中稍微了解一下,那么在最初的问题中提出的方式使用 lambdas 比使用覆盖的方法更可取。

因此,我确实认为它被认为是好的做法(M.Fowler 和 R.Martin 意义上的“干净” )而不是坏的做法。如果没有明确考虑,应该是。

也就是说,有一些持久的评论认为枚举本身不干净,因为它们诱使您使用不干净的开关语句(准确地说:代码气味,可能与干净相反) ,参考 M.Fowlers 第一版的《重构:改进现有代码的设计》。而且,你知道,是他创造了“干净”(和“代码气味”)这个词。但在2005 年的版本中,他收回了这一判断。

至于开关:必须考虑当您扩展枚举并忘记扩展所有开关时会发生什么。我和我的同事发现引入单元测试很有用,它遍历枚举的所有项目并测试,需要确保什么。该问题为 lambda 增强的 Enums 产生了另一个论点:在某些情况下,您可以保留开关 ( switch someValue ... case Enum.x: dosmthg()) 以支持调用映射函数 ( someValue.dosmthg())。

至于将这个问题包含在表达问题下的建议:

经过仔细检查,看起来表达问题与该问题根本无关。从链接:"The Expression Problem is a new name for an old problem. The goal is to define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety (e.g., no casts)."

因此,人们不能像建议的那样使用方法 A 和方法 B 来解决表达式问题,同样地,他们也不会遇到Hadwiger-Nelson 问题。表达式问题本身就是一个问题,也是函数式和面向对象语言的难题,每种语言都有不同的解决方案,独立于此处给出的上下文。这是一个声称既完整又有效的 java 解决方案,以及一个 Haskell 解决方案。确实是一件很复杂的事情。


推荐阅读