处理API时,处理“枚举”模式与java枚举共存的最佳方法是什么?

时间:2021-11-23 23:28:46

Suppose you're maintaining an API that was originally released years ago (before java gained enum support) and it defines a class with enumeration values as ints:

假设您正在维护最初几年前发布的API(在java获得枚举支持之前),并且它定义了一个枚举值为int的类:

public class VitaminType {
 public static final int RETINOL = 0;
 public static final int THIAMIN = 1;
 public static final int RIBOFLAVIN = 2;
}

Over the years the API has evolved and gained Java 5-specific features (generified interfaces, etc). Now you're about to add a new enumeration:

多年来,API已经发展并获得了Java 5特有的功能(通用接口等)。现在您要添加一个新的枚举:

public enum NutrientType {
 AMINO_ACID, SATURATED_FAT, UNSATURATED_FAT, CARBOHYDRATE;
}

The 'old style' int-enum pattern has no type safety, no possibility of adding behaviour or data, etc, but it's published and in use. I'm concerned that mixing two styles of enumeration is inconsistent for users of the API.

“旧式”int-enum模式没有类型安全性,不可能添加行为或数据等,但它已发布并正在使用中。我担心混合两种枚举方式对于API的用户来说是不一致的。

I see three possible approaches:

我看到三种可能的方法:

  • Give up and define the new enum (NutrientType in my fictitious example) as a series of ints like the VitaminType class. You get consistency but you're not taking advantage of type safety and other modern features.

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

  • Decide to live with an inconsistency in a published API: keep VitaminType around as is, and add NutrientType as an enum. Methods that take a VitaminType are still declared as taking an int, methods that take a NutrientType are declared as taking such.

    决定在已发布的API中存在不一致性:保持VitaminType不变,并添加NutrientType作为枚举。采用VitaminType的方法仍被声明为采用int,采用NutrientType的方法被声明为采用此类方法。

  • Deprecate the VitaminType class and introduce a new VitaminType2 enum. Define the new NutrientType as an enum.
    Congratulations, for the next 2-3 years until you can kill the deprecated type, you're going to deal with deprecated versions of every single method that took a VitaminType as an int and adding a new foo(VitaminType2 v) version of each. You also need to write tests for each deprecated foo(int v) method as well as its corresponding foo(VitaminType2 v) method, so you just multiplied your QA effort.

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

What is the best approach?

什么是最好的方法?

5 个解决方案

#1


3  

Personal opinion is that it's probably not worth the effort of trying to convert. For one thing, the "public static final int" idiom isn't going away any time soon, given that it's sprinkled liberally all over the JDK. For another, tracking down usages of the original ints is likely to be really unpleasant, given that your classes will compile away the reference so you're likely not to know you've broken anything until it's too late (by which I mean

个人意见认为,尝试转换可能不值得。一方面,“public static final int”成语不会很快消失,因为它在整个JDK中大量涌现。另一方面,追踪原始整数的用法可能真的很不愉快,因为你的课程会编译掉参考文献,所以你可能不会知道你已经破坏了任何东西,直到为时已晚(我的意思是

class A
   {
       public static final int MY_CONSTANT=1
   }

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

gets compiled into

被编译成

i+=1

So if you rewrite A you may not ever realize that B is broken until you recompile B later.

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

It's a pretty well known idiom, probably not so terrible to leave it in, certainly better than the alternative.

这是一个众所周知的习语,可能不是那么糟糕,留下它,肯定比替代方案更好。

#2


6  

How likely is it that the API consumers are going to confuse VitaminType with NutrientType? If it is unlikely, then maybe it is better to maintain API design consistency, especially if the user base is established and you want to minimize the delta of work/learning required by customers. If confusion is likely, then NutrientType should probably become an enum.

API消费者将VitaminType与NutrientType混淆的可能性有多大?如果不太可能,那么维持API设计的一致性可能会更好,特别是如果建立了用户群并且您希望最小化客户所需的工作/学习的增量。如果可能出现混淆,那么NutrientType应该可能成为一个枚举。

This needn't be a wholesale overnight change; for example, you could expose the old int values via the 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();

}

This allows you to update the API and gives you some control over when to deprecate the old type and scheduling the switch-over in any code that relies on the old type internally.

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

Some care is required to keep the literal values in sync with those that may have been in-lined with old consumer code.

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

#3


1  

There is a rumor that the creator of "make" realized that the syntax of Makefiles was bad, but felt that he couldn't change it because he already had 10 users.

有传言说“make”的创建者意识到Makefiles的语法很糟糕,但觉得他无法改变它,因为他已有10个用户。

Backwards compatibility at all costs, even if it hurts your customers, is a bad thing. SO can't really give you a definitive answer on what to do in your case, but be sure and consider the cost to your users over the long term.

即使会伤害您的客户,不惜一切代价向后兼容也是一件坏事。因此,无法真正为您提供有关如何处理案例的明确答案,但请务必考虑长期用户的成本。

Also think about ways you can refactor the core of your code will keeping the old integer based enums only at the outer layer.

还要考虑一下你可以重构代码核心的方法,只在外层保留旧的基于整数的枚举。

#4


1  

Wait for the next major revision, change everything to enum and provide a script (sed, perl, Java, Groovy, ...) to convert existing source code to use the new syntax.

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

Obviously this has two drawbacks:

显然这有两个缺点:

  • No binary compatibility. How important this one is depends on the use cases, but can be acceptable in the case of a new major release
  • 无二进制兼容性。这个有多重要取决于用例,但在新的主要版本的情况下可以接受

  • Users have to do some work. If the work is simple enough, then this too may be acceptable.
  • 用户必须做一些工作。如果工作足够简单,那么这也是可以接受的。

In the meantime, add new types as enums and keep old types as ints.

在此期间,将新类型添加为枚举并将旧类型保留为整数。

#5


0  

The best would be if you could just fix the published versions, if possible. In my opinion consistency would be the best solution, so you would need to do some refactoring. I personally don't like deprecated things, because they get into way. You might be able to wait until a bigger version release and use those ints until then, and refactor everything in a big project. If that is not possible, you might consider yourself stuck with the ints, unless you create some kinds of wrappers or something.

如果可能的话,最好的方法是修改已发布的版本。在我看来,一致性将是最好的解决方案,因此您需要进行一些重构。我个人不喜欢弃用的东西,因为它们已经开始了。您可能要等到更大版本发布并在此之前使用这些内容,并重构一个大项目中的所有内容。如果这是不可能的,你可能会认为自己坚持使用整数,除非你创建了一些包装或其他东西。

If nothing helps but you still evolve the code, you end up losing consistency or living with the deprecated versions. In any case, usually at least at some point of time people become fed up with old stuff if it has lost it's consistency and create new from scratch... So you would have the refactoring in the future no matter what.

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

The customer might scrap the project and buy an other product, if something goes wrong. Usually it is not the customer's problem can you afford refactoring or not, they just buy what is appropriate and usable to them. So in the end it is a tricky problem and care needs to be taken.

如果出现问题,客户可能会废弃项目并购买其他产品。通常不是客户的问题,你能否承担重构,他们只是购买适合他们的东西。所以最终这是一个棘手的问题,需要注意。

#1


3  

Personal opinion is that it's probably not worth the effort of trying to convert. For one thing, the "public static final int" idiom isn't going away any time soon, given that it's sprinkled liberally all over the JDK. For another, tracking down usages of the original ints is likely to be really unpleasant, given that your classes will compile away the reference so you're likely not to know you've broken anything until it's too late (by which I mean

个人意见认为,尝试转换可能不值得。一方面,“public static final int”成语不会很快消失,因为它在整个JDK中大量涌现。另一方面,追踪原始整数的用法可能真的很不愉快,因为你的课程会编译掉参考文献,所以你可能不会知道你已经破坏了任何东西,直到为时已晚(我的意思是

class A
   {
       public static final int MY_CONSTANT=1
   }

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

gets compiled into

被编译成

i+=1

So if you rewrite A you may not ever realize that B is broken until you recompile B later.

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

It's a pretty well known idiom, probably not so terrible to leave it in, certainly better than the alternative.

这是一个众所周知的习语,可能不是那么糟糕,留下它,肯定比替代方案更好。

#2


6  

How likely is it that the API consumers are going to confuse VitaminType with NutrientType? If it is unlikely, then maybe it is better to maintain API design consistency, especially if the user base is established and you want to minimize the delta of work/learning required by customers. If confusion is likely, then NutrientType should probably become an enum.

API消费者将VitaminType与NutrientType混淆的可能性有多大?如果不太可能,那么维持API设计的一致性可能会更好,特别是如果建立了用户群并且您希望最小化客户所需的工作/学习的增量。如果可能出现混淆,那么NutrientType应该可能成为一个枚举。

This needn't be a wholesale overnight change; for example, you could expose the old int values via the 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();

}

This allows you to update the API and gives you some control over when to deprecate the old type and scheduling the switch-over in any code that relies on the old type internally.

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

Some care is required to keep the literal values in sync with those that may have been in-lined with old consumer code.

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

#3


1  

There is a rumor that the creator of "make" realized that the syntax of Makefiles was bad, but felt that he couldn't change it because he already had 10 users.

有传言说“make”的创建者意识到Makefiles的语法很糟糕,但觉得他无法改变它,因为他已有10个用户。

Backwards compatibility at all costs, even if it hurts your customers, is a bad thing. SO can't really give you a definitive answer on what to do in your case, but be sure and consider the cost to your users over the long term.

即使会伤害您的客户,不惜一切代价向后兼容也是一件坏事。因此,无法真正为您提供有关如何处理案例的明确答案,但请务必考虑长期用户的成本。

Also think about ways you can refactor the core of your code will keeping the old integer based enums only at the outer layer.

还要考虑一下你可以重构代码核心的方法,只在外层保留旧的基于整数的枚举。

#4


1  

Wait for the next major revision, change everything to enum and provide a script (sed, perl, Java, Groovy, ...) to convert existing source code to use the new syntax.

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

Obviously this has two drawbacks:

显然这有两个缺点:

  • No binary compatibility. How important this one is depends on the use cases, but can be acceptable in the case of a new major release
  • 无二进制兼容性。这个有多重要取决于用例,但在新的主要版本的情况下可以接受

  • Users have to do some work. If the work is simple enough, then this too may be acceptable.
  • 用户必须做一些工作。如果工作足够简单,那么这也是可以接受的。

In the meantime, add new types as enums and keep old types as ints.

在此期间,将新类型添加为枚举并将旧类型保留为整数。

#5


0  

The best would be if you could just fix the published versions, if possible. In my opinion consistency would be the best solution, so you would need to do some refactoring. I personally don't like deprecated things, because they get into way. You might be able to wait until a bigger version release and use those ints until then, and refactor everything in a big project. If that is not possible, you might consider yourself stuck with the ints, unless you create some kinds of wrappers or something.

如果可能的话,最好的方法是修改已发布的版本。在我看来,一致性将是最好的解决方案,因此您需要进行一些重构。我个人不喜欢弃用的东西,因为它们已经开始了。您可能要等到更大版本发布并在此之前使用这些内容,并重构一个大项目中的所有内容。如果这是不可能的,你可能会认为自己坚持使用整数,除非你创建了一些包装或其他东西。

If nothing helps but you still evolve the code, you end up losing consistency or living with the deprecated versions. In any case, usually at least at some point of time people become fed up with old stuff if it has lost it's consistency and create new from scratch... So you would have the refactoring in the future no matter what.

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

The customer might scrap the project and buy an other product, if something goes wrong. Usually it is not the customer's problem can you afford refactoring or not, they just buy what is appropriate and usable to them. So in the end it is a tricky problem and care needs to be taken.

如果出现问题,客户可能会废弃项目并购买其他产品。通常不是客户的问题,你能否承担重构,他们只是购买适合他们的东西。所以最终这是一个棘手的问题,需要注意。