简体   繁体   English

强制实现枚举的Java接口-如何处理?

[英]Java Interface that forces implementation of an enum - How to approach?

I have a situation where I would like to used an instance of an object called Abstraction, which would be a Java interface looking something like: 我遇到一种情况,我想使用一个称为Abstraction的对象的实例,该实例将是一个类似于以下内容的Java接口:

public interface Abstraction {
   public enum Actions {
   }
}

The idea being that any class implementing Abstraction has to implement enum Actions (I realise this doesn't work). 这个想法是任何实现Abstraction的类都必须实现enum Actions(我意识到这是行不通的)。

A class implementing the interface may look like: 实现该接口的类可能类似于:

public class AbstractionA implements Abstraction {
   public enum Actions {
    f, c, r, a
   }
}

In another class I would want to create an Abstraction object like: 在另一个类中,我想创建一个Abstraction对象,例如:

Abstraction abs = new AbstractionA();

and then be able to access the enum values applicable to the Abstraction object created, eg 然后能够访问适用于创建的Abstraction对象的枚举值,例如

abs.Action.r;

I realise my approach to this is all wrong but cannot see an appropriate way to handle this type of situation. 我意识到我的方法是完全错误的,但是看不到适当的方法来处理这种情况。 How can I implement something like this where different implementations of the interface have a varying subset of the options I would generally want to put in an enum? 在接口的不同实现具有我通常希望放入枚举的选项的不同子集的情况下,如何实现这样的事情? Perhaps I can implement the enum with all possible options in the interface and then somehow restrict implementations of the interface to using a subset of those enum values? 也许我可以在接口中使用所有可能的选项来实现枚举,然后以某种方式将接口的实现限制为使用这些枚举值的子集?

EDIT: Another example implementation might be 编辑:另一个示例实现可能是

public class AbstractionB implements Abstraction {
   public enum Actions {
    f, c, b, r, a
   }
}

I think I have figured out a way forward with this: 我想我已经找到了解决这个问题的方法:

public interface Abstraction {
   public enum Actions {
    f, c, b, r, s, a  
   }
   public Actions[] availableActions();
}

Then implement with: 然后执行:

public class HunlAbstractionA implements Abstraction{
   @Override
   public Actions[] availableActions()
   {
    Actions[] actions = new Actions[] {Actions.f, Actions.c, Actions.r, Actions.a};
    return actions;
   }
}

This way I have access to all possible actions listed in the interfaces enum and can make checks to ensure an Action to be dealt with is one of the availableActions for the created class. 这样,我可以访问接口枚举中列出的所有可能的动作,并且可以进行检查以确保要处理的动作是所创建类的availableAction之一。

Interface is a contract that you want anyone to provide an implementation of that contract. 接口是您希望任何人提供该合同的实施的合同。 In your example code you do not have a method but a definition of a enum called Action. 在示例代码中,您没有方法,但是有一个名为Action的枚举的定义。

Generally enum is a set of constants hence we do not expect multiple classes to come up with different implementations of the same constant. 通常enum是一组常量,因此我们不希望多个类提出相同常量的不同实现。

So you might want to rethink about your approach and figure out a better way. 因此,您可能需要重新考虑您的方法并找出更好的方法。 Hope this will help moving you in correct direction. 希望这将有助于您朝正确的方向前进。

Recommendation 建议

I'd recommend the following approach. 我建议采用以下方法。

This approach uses a combination of generics and reflection to help explicitly indicate the need to implement or choose an appropriate enum, it also gives you the option of preserving information about the enum type whilst hiding all other information about the specific Abstraction implementation. 此方法结合使用泛型和反射来帮助明确指出实现或选择适当的枚举的需要,它还为您提供了保留有关枚举类型的信息,同时隐藏了有关特定抽象实现的所有其他信息的选项。

/**
 * An abstraction with an implementation-defined enum
 * @param <E> your custom enum.
 */
interface Abstraction<E extends Enum> {

    //this gives you the enum constants as a list
    Class<E> getEnumType();

}

class AbstractionA implements Abstraction<AbstractionA.EnumA> {
    enum EnumA {
        FOO,
        BAR
    }

    @Override
    public Class<EnumA> getEnumType() {
        return EnumA.class;
    }
}

class AbstractionB implements Abstraction<AbstractionB.EnumB> {
    enum EnumB {
        FOO,
        BAR
    }

    @Override
    public Class<EnumB> getEnumType() {
        return EnumB.class;
    }
}

Note that unfortunately we can supply a default implementation of getEnumType() due to type erasure . 注意,不幸的是,由于类型 Erase,我们可以提供getEnumType()的默认实现。

Usage Example 使用范例

class Main {
    public static void main(String[] args) {
        Abstraction myAbstractionA = new AbstractionA();
        Abstraction<AbstractionB.EnumB> myAbstractionB = new AbstractionB();

        Class enumAType = myAbstractionA.getEnumType();
        Class<AbstractionB.EnumB> enumBType = myAbstractionB.getEnumType();
        Object[] enumsA = enumAType.getEnumConstants();
        AbstractionB.EnumB[] enumsB = enumBType.getEnumConstants();
        System.out.printf("Enums of the same order are still non-identical: %s", enumsA[0].equals(enumsB[0]));
        System.out.println();

        Enum enumA = ((Enum)enumsA[0]);
        Enum enumB = ((Enum)enumsB[1]);
        System.out.printf("We can get enum constants in order, and get the orderinal of the enum: A=%s, B=%s", enumA.ordinal(), enumB.ordinal());
        System.out.println();

        enumA = Enum.valueOf(enumAType, "FOO");
        enumB = Enum.valueOf(enumBType, "BAR");
        System.out.printf("We can get enum constants by name and get the name out of the enum: A=%s, B=%s", enumA.name(), enumB.name());
        System.out.println();
    }
}

Alternatives 备择方案

If you can use an abstract class instead of an interface, you may prefer a solution similar to this related answer . 如果可以使用抽象类而不是接口,则可能更喜欢类似于此相关答案的解决方案。

Edit : If you have a common set of constants you want to share across your actions, you should probably use a global/shared enum for those constants and define only the extensions themselves in the custom Abstractions. 编辑 :如果您要在所有操作中共享一组通用常量,则可能应该对这些常量使用全局/共享枚举,并在自定义抽象中仅定义扩展本身。 If you cast them all to Enum and use .equals() as needed, this should work in most cases. 如果将它们全部转换为Enum并根据需要使用.equals() ,则在大多数情况下应该可以使用。

Background 背景

As you have stated you know, it is not possible to place member objects (variable or classes) of an interface. 正如你说你知道,这是不可能将一个接口的成员对象(变量或类)。

However, the good news is that java actually supports the behaviour you want pretty well. 但是,好消息是Java实际上很好地支持了您想要的行为。

There are 3 key features that relate to my recommendation: 与我的建议相关的3个关键功能:

Enums are Objects 枚举是对象

Firstly, enums in java are fully-fledged Object s, which all extend java.lang.Enum , and all implement .equals() . 首先,java中的枚举是成熟的Object ,它们都扩展了java.lang.Enum ,并且都实现了.equals()

So, you can store different any enum class' values in a variable of type java.lang.Enum and compare them with .equals() . 因此,您可以将不同的任何枚举类的值存储在java.lang.Enum类型的变量中,并将它们与.equals()进行比较。

And , if you want to pretend that values of different enum classes are the same because they share the same name (or are the nth constant in their respective class), you can do that too. 并且 ,如果您要假装不同枚举类的值是相同的,因为它们共享相同的名称(或者是它们各自类中的第n个常量),那么您也可以这样做。

Note that this also means that custom enums can contain complex data and behaviour like any other class, beyond it's use as a unique identifier. 请注意,这也意味着自定义枚举可以像其他任何类一样包含复杂的数据和行为,除了用作唯一标识符之外。

See the Enum API documentation for details. 有关详细信息,请参见Enum API文档

Java Reflection Java反射

Secondly, Java has extensive reflection support. 其次,Java具有广泛的反射支持。 For our purposes, java.lang.Class has a method called getEnumConstants() for getting the enum constants (or null if the class is not an enum). 就我们的目的而言, java.lang.Class具有一个名为getEnumConstants()的方法,用于获取枚举常量(如果该类不是枚举,则为null )。 See the Class API documentation for details. 有关详细信息,请参见类API文档

Cyclic Dependancies 循环依赖

Thirdly, at least when it comes to generics, Java is permissive when it comes to cyclic dependancies, so you can define a generic interface depends on a specialisation of that generic. 第三,至少在泛型方面,Java在循环依赖方面是允许的,因此您可以根据泛型的特殊性来定义泛型接口。 Java won't mind. Java不会介意的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM