"int enum" 模式和 Java 枚举在 API 发展中的共存最佳方式是什么?

5
假设你正在维护一个API,这个API在几年前发布(当时Java还没有支持"enum"),并且它定义了一个将枚举值作为整数的类:
public class VitaminType {
 public static final int RETINOL = 0;
 public static final int THIAMIN = 1;
 public static final int RIBOFLAVIN = 2;
}

多年来,API 已经发展并获得了 Java 5 特定的功能(泛型接口等)。现在你将要添加一个新的枚举:
public enum NutrientType {
 AMINO_ACID, SATURATED_FAT, UNSATURATED_FAT, CARBOHYDRATE;
}

“旧式” int-enum 模式没有类型安全性,没有添加行为或数据的可能性等,但是它已经发布并且在使用。我担心混合两种枚举风格对 API 的用户来说是不一致的。 我看到有三种可能的方法:
1. 放弃并像虚构例子中的 VitaminType 类一样将新枚举(NutrientType)定义为一系列 int。这样会得到一致性,但你没有利用类型安全和其他现代特性。
2. 决定在已发布的 API 中保留不一致性:保留 VitaminType 并按原样增加 NutrientType 作为枚举。接受一个 VitaminType 的方法仍然声明为接受一个 int,接受一个 NutrientType 的方法也是如此。
3. 废弃 VitaminType 类并引入一个新的 VitaminType2 枚举。定义新的 NutrientType 作为枚举。祝贺你,未来 2-3 年直到你可以消除废弃类型之前,你将处理每个以 int 形式获取 VitaminType 的方法的废弃版本,并添加每个 foo(VitaminType2 v) 版本。你还需要为每个废弃的 foo(int v) 方法编写测试以及其相应的 foo(VitaminType2 v) 方法,因此您将使 QA 工作量增加。
哪种方法最好?
5个回答

6

API使用者会混淆VitaminType和NutrientType的可能性有多大?如果不太可能,那么最好保持API设计的一致性,特别是如果用户基础已经建立,您想要减少客户所需的工作/学习差异。如果容易混淆,则NutrientType应该成为一个枚举

这不需要是整个夜间的变化;例如,您可以通过枚举公开旧的int值:

public enum Vitamin {

    RETINOL(0), THIAMIN(1), RIBOFLAVIN(2);

    private final int intValue;

    Vitamin(int n) {
        intValue = n;
    }

    public int getVitaminType() {
        return intValue;
    }

    public static Vitamin asVitamin(int intValue) {
        for (Vitamin vitamin : Vitamin.values()) {
            if (intValue == vitamin.getVitaminType()) {
                return vitamin;
            }
        }
        throw new IllegalArgumentException();
    }

}

/** Use foo.Vitamin instead */
@Deprecated
public class VitaminType {

    public static final int RETINOL = Vitamin.RETINOL.getVitaminType();
    public static final int THIAMIN = Vitamin.THIAMIN.getVitaminType();
    public static final int RIBOFLAVIN = Vitamin.RIBOFLAVIN.getVitaminType();

}

这允许您更新API,并控制何时弃用旧类型以及在任何依赖于旧类型的代码中安排切换。

需要注意的是,要确保字面值与旧消费者代码中可能已内联的字面值同步。


3

个人观点是,尝试转换可能不值得。首先,“public static final int”惯用语很快不会消失,因为它在JDK中随处可见。其次,追踪原始int的用法可能非常不愉快,因为您的类将编译引用,所以您可能不知道您已经破坏了任何东西,直到为时已晚(我的意思是)

class A
   {
       public static final int MY_CONSTANT=1
   }

   class B
   {
           ....
           i+=A.MY_CONSTANT; 
   }

编译后变成

i+=1

如果你重写了A,你可能直到后来重新编译B才发现B出现故障。

这是一个相当知名的惯用语,也许留下它并不那么糟糕,肯定比其他选择更好。


1

有传言称“make”创建者意识到Makefile的语法不好,但他觉得自己不能改变它,因为他已经有了10个用户。

无论如何都要保持向后兼容,即使这会伤害你的客户,这是一件坏事。SO无法给出您的情况下该怎么做的明确答案,但请务必考虑长期对用户的成本。

同时,请考虑在保留旧的基于整数的枚举类型的外层的情况下,重构代码核心的方法。


1

等待下一次重大修订,将所有内容更改为枚举,并提供一个脚本(sed、perl、Java、Groovy等)来将现有源代码转换为使用新语法。

显然这有两个缺点:

  • 没有二进制兼容性。这个缺点的重要性取决于使用情况,在新的主要版本中可能是可以接受的。
  • 用户需要做一些工作。如果工作足够简单,这也可能是可以接受的。

同时,添加新类型作为枚举,并将旧类型保留为整数。


0
最好的情况是,如果可能的话,请修复已发布版本。我认为一致性是最好的解决方案,因此您需要进行一些重构。我个人不喜欢废弃的东西,因为它们会妨碍使用。您可以等待更大的版本发布,并在此之前使用这些整数,在大型项目中重构所有内容。如果不可能,除非创建某种包装器或其他东西,否则您可能会被困在整数中。
如果没有任何帮助,但您仍然发展了代码,则最终会失去一致性或者不得不使用废弃版本。无论如何,通常至少在某个时候,如果旧东西失去了一致性,人们都会受够了并从头开始创建新的...所以无论如何,您将来都需要进行重构。
如果出了问题,客户可能会放弃该项目并购买其他产品。通常情况下,是否能承担重构并不是客户的问题,他们只会购买适合他们的东西。因此,最后这是一个棘手的问题,需要谨慎处理。

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