每个枚举成员的内部类?

8

我不确定我想要的是否可行,但我试图创建一个枚举,其中每个成员都有自己的内部类。这些内部类将全部具有相同的名称Context,但将分别实现。

理想情况下,我希望它们可以这样使用:

private handleType (MyEnum type) {
    switch (type) {

        case ENUM_VAL1:
            MyEnum.ENUM_VAL1.Context context = new MyEnum.ENUM_VAL1.Context();
            handleContext1(context);
            break;

        case ENUM_VAL2:
            MyEnum.ENUM_VAL2.Context context = new MyEnum.ENUM_VAL1.Context();
            handleContext2(context);
            break;

        case ENUM_VAL3:
            MyEnum.ENUM_VAL3.Context context = new MyEnum.ENUM_VAL1.Context();
            handleContext3(context);
            break;

        default:
            break;
}

开放其他实现方式。但基本上我需要一个可切换的枚举类型,其中每个成员都有一个“值”(1、2、3…),并且还有一种将该枚举类型与具有构造函数的唯一类相关联的方法。
编辑:一些背景信息。这将在两个通过JSON HTTP请求通信的服务之间使用。请求将包含一些元数据,其中一个字段是映射到该枚举类型的整数。上下文是一个POJO,但对于每个ENUM_VALUE都不同。基本上,上下文将被构建并序列化为JSON。这个JSON实际上只是顶层JSON请求中称为上下文的字符串字段。在接收服务上,将会有一个ENUM_VALUE开关,其中上下文会被适当解码,然后分派到其适当的处理程序。
编辑2:这个枚举类型将在两个服务之间共享。
编辑3:以下是我尝试做的事情更详细的说明。
MyServiceRequest:
public class MyServiceRequest {
    String meta1;
    String meta2;
    int typeID;
    String context;
}

生成请求:
MyServiceRequest req = new MyServiceRequest();
req.meta1 = ...
req.meta2 = ...
req.typeID = MyEnum.ENUM_VALUE.getCode(); // int

MyEnum.ENUM_VALUE.Context context = new MyEnum.ENUM_VALUE.Context(); // factory would be fine as well
... // populate context
req.context = toJSON(context);
requestJSON = toJSON(req);
post(requestJSON);

解码请求:

MyServiceRequest req = ...
MyEnum type = new MyEnum(req.typeID);
switch(type) {
    case ENUM_VALUE:
        MyEnum.ENUM_VALUE.Context context = fromJSON(req.context, MyEnum.ENUM_VALUE.Context.class);
        doSomething(context);

2
在我看来,每个不同的Context实现都有一个方法听起来像是一个糟糕的设计。我会将Context作为接口或抽象类,并让每个子类实现它添加具体行为(即多态性)。然后,您只需要一个handleContext方法。 - fps
@FedericoPeraltaSchaffner 在编辑中添加了一些背景。 - thedarklord47
1
你的修改并没有帮助我理解为什么你要追求所描述的设计,或者从枚举中获得什么优势,更不用说内部类了。这一切似乎都过于复杂了。为什么不跳过枚举,只为每个请求类型创建一个单独的类呢? - John Bollinger
老实说,我来自动态语言的世界,在那里这样的事情都是微不足道的。当上下文对象的类型可能会变化时,您会如何处理在单个端点接收到每个请求的不同类? - thedarklord47
一个标准的解决方案是使用一个接口,该接口具有字符串值的getter或方法(您能处理此对象吗?)。然后,您只需要一个字典或列表,并遍历元素以找到正确的元素。您还可以使用服务定位器来分离类的实现和使用。 - Voo
显示剩余2条评论
4个回答

9
你可以做的另一件事是让你的枚举实现 Supplier<Context>。现在每个项目都必须声明一个 get() 方法来创建相应的 Context 子类型。
enum MyEnum implements Supplier<Context>{
   FOO{ @Override public Context get(){ return new FooContext(); } },
   BAR{ @Override public Context get(){ return new BarContext(); } }
}

这将使您的客户端代码更加简单:
private void handleType (MyEnum type) {
    handleContext(type.get());
}

有趣。不过我仍然需要类型具有相应的整数值。 - thedarklord47
1
在这种情况下,您可以在“上下文”中拥有一个抽象方法,该方法返回该整数值,每个子类都返回不同的值。- @thedarklord47 - fps
1
@FedericoPeraltaSchaffner 没错。那就是抽象工厂模式的精髓所在。 - Sean Patrick Floyd
@SeanPatrickFloyd 是的,这是有道理的,因为 Supplier<T> 实际上是 T 实例的工厂。 - fps

4

为什么要使用内部类?

你可以简单地拥有一个字段context,为每个枚举常量初始化不同的值,例如:

public enum Whatever {
  A(new AContext), B... 

  private final Context context;

  private Whatever(Context context) {
    this.context = context;
 .... 

上下文本身就是复杂对象。基本上,我需要一种将这些上下文对象(以及它们的不同实现)与枚举相关联的方法。 - thedarklord47
因此,让枚举类拥有一个上下文对象。在这里引入子类没有任何好处。 - GhostCat
你的回答中的AContext是什么? - thedarklord47
一个特定的Context子类。或者也许不是AContext,而是Context(A)。意思是:它只是一个占位符,用于指示解决此问题的一种方法。重点是你不需要在枚举中使用子类! - GhostCat

1
我不建议为每个枚举使用单独的内部类,只需使用单独的实现即可。像下面这样的东西可能是最好的方法,然后你就不必使用switch语句了。因为你可以直接在type变量上调用getContext()函数:
enum MyEnum{

    A(new Context(){
        // my implementation
    }),
    B(new Context(){
        // my other implementation
    }),
    ;

    private final Context context;

    MyEnum(Context context){
        this.context = context;
    }

    public Context getContext(){
        return context;
    }

    public interface Context{
        // do something
    }
}

1
如果上下文是不可变的,那么这种实现就是有意义的。 - Sean Patrick Floyd
1
@SeanPatrickFloyd 由于枚举是静态的,我假设上下文在运行时不应该更改。 - Lino
Context类应该是静态的。我需要创建和修改Context对象。这个修改将在handleContextX()中发生。 - thedarklord47
@Lino 我同意你的观点,但强调这一点是为了未来的读者。 - Sean Patrick Floyd

1
你所描述的最大问题是,作用域仅限于单个枚举元素的类没有在该元素之外可解析的名称。这使得在枚举值之外使用new运算符实例化此类变得不可能,也无法声明任何以该类为参数或返回类型的方法。
但是,您可以通过声明内部类实现的接口类型,并提供工厂方法来充当获取实例的构造函数,从而在很大程度上解决这个问题。例如:
enum MyEnum {
    ENUM_VAL1 {
        class Context implements MyEnum.Context {
            public void doSomething() {
                System.out.println(1);
            }
        }

        public MyEnum.Context createContext() {
            return new Context();
        }
    },
    ENUM_VAL2 {
        class Context implements MyEnum.Context {
            public void doSomething() {
                System.out.println(2);
            }
        }

        public MyEnum.Context createContext() {
            return new Context();
        }
    };

    interface Context {
        public void doSomething();
    }

    public abstract Context createContext();
}

public class EnumScope {

    private void handleContext1(MyEnum.Context context) {
        context.doSomething();
    }

    private void handleContext2(MyEnum.Context context) {
        context.doSomething();
    }

    private void handleType(MyEnum type) {
        MyEnum.Context context = type.createContext();

        switch (type) {
            case ENUM_VAL1:
                handleContext1(context);
                break;
            case ENUM_VAL2:
                handleContext2(context);
                break;
        }
    }
}

我认为这有点可疑,特别是具有特定于特定枚举值的方法,这些方法实际上并不属于这些枚举值。可能有完全不同的方法可以更好地解决问题,但您描述的问题太过笼统,我们无法建议这样的替代方法。


更新

经过考虑您对问题所做的修改以及随后的评论,我倾向于坚持我的评估,即您提出的方案有些可疑。

退一步考虑问题,从更广泛的角度考虑。您正在生成、序列化(为JSON)、反序列化和消耗几种类型的请求(目前通过内部ID代码进行区分)。使用适当属性的类来表示每种类型的请求是有意义的,包括每种类型的不同上下文数据的属性。如果有一些故意的共性,那么也许应该实现一个描述它们的公共接口,甚至扩展一个公共基类。

完成这些后,JSON序列化/反序列化就是一个已解决的问题(不止一次)。除非您喜欢重新发明轮子,否则我倾向于建议使用Google GSON。我需要承认我个人没有太多GSON的经验,但它非常流行,在这里您会看到很多关于它的问题(和答案)。您还可以找到一些好的在线教程。


为什么要为每个枚举声明类?只需使用匿名类即可缩短代码,而且实际上做的是同样的事情。 - Lino
1
这是一个很好的观察,@Lino。在我所呈现的内容中,使用命名内部类并没有特别的优势。然而,原则上,这样的类可以作为枚举元素内部的变量、参数或返回类型(这需要命名类)。因此,我正在演示您确实可以在枚举元素内声明命名内部类。 - John Bollinger
从那个角度看,这确实有道理 :) - Lino
我在第三次编辑中添加了一些额外的解释。你看到有什么明显的替代方案吗?我宁愿不写可疑的代码哈哈 - thedarklord47
@thedarklord47,我已经更新了我的答案,提出了一个(非常不同的)替代方法。 - John Bollinger

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