5

假设您正在维护一个最初在几年前发布的 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 的用户来说混合两种枚举风格是不一致的。

我看到了三种可能的方法:

  • 放弃并将新的枚举(NutrientType在我的虚构示例中)定义为一系列整数,如VitaminType类。您获得了一致性,但您没有利用类型安全和其他现代功能。

  • 决定忍受已发布 API 中的不一致:保持VitaminType原样,并添加NutrientTypeenum. 采用 aVitaminType的方法仍被声明为采用 int,采用 a 的方法NutrientType被声明为采用 int。

  • 弃用VitaminType该类并引入一个新的VitaminType2枚举。将新定义NutrientType为枚举。
    恭喜,在接下来的 2-3 年中,直到您可以杀死已弃用的类型,您将处理每个将 aVitaminType作为 int 的方法的弃用版本并添加每个方法的新foo(VitaminType2 v)版本。您还需要为每个已弃用foo(int v)的方法及其相应的foo(VitaminType2 v)方法编写测试,因此您只是增加了 QA 工作量。

最好的方法是什么?

4

5 回答 5

6

API 消费者将 VitaminType 与 NutrientType 混淆的可能性有多大?如果不太可能,那么保持 API 设计的一致性可能会更好,特别是如果用户基础已经建立并且您希望最大限度地减少客户所需的工作/学习增量。如果可能发生混淆,那么 NutrientType 应该可能变成一个enum

这不一定是一夜之间的大规模改变;例如,您可以通过枚举公开旧的 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 并让您对何时弃用旧类型以及在内部依赖旧类型的任何代码中安排切换进行一些控制。

需要注意保持文字值与那些可能与旧消费者代码内联的值同步。

于 2008-12-04T17:09:37.663 回答
3

个人观点是,尝试转换可能不值得。一方面,“public static final int”习语不会很快消失,因为它广泛地散布在 JDK 中。另一方面,跟踪原始整数的使用可能真的很不愉快,因为你的类会编译掉引用,所以你可能不知道你已经破坏了任何东西,直到为时已晚(我的意思是

class A
   {
       public static final int MY_CONSTANT=1
   }

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

被编译成

i+=1

因此,如果您重写 A,您可能永远不会意识到 B 已损坏,直到您稍后重新编译 B。

这是一个非常有名的习语,把它留在里面可能并不那么糟糕,当然比其他的要好。

于 2008-12-04T15:49:44.237 回答
1

有传言说,“make”的创建者意识到 Makefile 的语法不好,但觉得无法更改,因为他已经有 10 个用户。

不惜一切代价向后兼容,即使它会伤害您的客户,也是一件坏事。所以不能真正给你一个明确的答案,告诉你在你的情况下该怎么做,但一定要考虑长期给你的用户带来的成本。

还要考虑重构代码核心的方法,将旧的基于整数的枚举保留在外层。

于 2008-12-04T15:49:40.743 回答
1

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

显然,这有两个缺点:

  • 没有二进制兼容性。这个有多重要取决于用例,但在新的主要版本的情况下是可以接受的
  • 用户必须做一些工作。如果工作足够简单,那么这也是可以接受的。

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

于 2008-12-04T16:00:10.770 回答
0

如果可能的话,最好的办法是修复已发布的版本。在我看来,一致性将是最好的解决方案,因此您需要进行一些重构。我个人不喜欢被弃用的东西,因为它们会碍事。您也许可以等到更大的版本发布并在此之前使用这些整数,然后重构一个大项目中的所有内容。如果那不可能,您可能会认为自己被 int 困住了,除非您创建某种包装器或其他东西。

如果没有任何帮助,但您仍在改进代码,那么您最终会失去一致性或使用已弃用的版本。无论如何,通常至少在某个时候人们会厌倦旧的东西,如果它失去了它的一致性并从头开始创建新的东西......所以无论如何你都会在未来进行重构。

如果出现问题,客户可能会放弃该项目并购买其他产品。通常,您是否负担得起重构不是客户的问题,他们只是购买对他们合适且可用的东西。所以最后这是一个棘手的问题,需要小心。

于 2008-12-04T15:56:02.680 回答