使用Reflection动态调用正确的setter方法

时间:2015-09-02 21:11:01

标签: java reflection enums

我正在使用一个我无法修改的库,它有一个包含以下 /** * Does stuff * * @param blah stuff needing done */ public doStuff(blah: string) {} 和setter的类。

Enum

注意每个public class MyClass { public enum MyEnum { ClassA, ClassB, ClassC ... } private SomeEnum myEnum; private Interface ifc; // parent of ClassA, ClassB, ClassC, etc. public ClassA setClassA(ClassA classA) { ifc = classA; myEnum= SomeEnum.ClassA; } public ClassB setClassB(ClassB classB) { ifc = classB; myEnum= SomeEnum.ClassB; } public ClassC setClassC(ClassC classC) { ifc = classC; myEnum= SomeEnum.ClassC; } // ... more of these setters } 名称是如何实现Enum的相应类名的字符串文字匹配,以及每个类名如何具有自己的特定setter。

调用此代码的代码非常简单,正如您想象的那样:

Interface

Interface ifc = someCallToGetAnImpl(); MyClass myClass = new MyClass(); myClass.set???(ifc); 的实现有批次,我不能保证库的未来版本不会添加更多。所以我期待创建一个可以动态派生并调用正确的setter的函数。

当然,我可以构建一个大的Interface块,但是当构建if... else if...的新实现时,需要更改软件。我还考虑过使用Class.getDeclaredMethod(String name, Class<?>... parameterTypes)来构建Interface参数,并尝试使用

name

让软件保持动态,但看起来很糟糕。

欢迎任何干净,生产质量的建议或方法。

4 个答案:

答案 0 :(得分:2)

您正在考虑的方法,通过Class.getDeclaredMethod(String name, Class... parameterTypes)等方法使用Reflection是解决您所描述问题的正确方法。这是一个kludgy问题,它值得一个kludgy方法。

我要补充的一件事是,你编写单元测试来验证你对该类中模式的假设,这样如果他们添加一个不遵循模式的方法,你会收到警告。例如,如果添加了新的枚举值,但是没有方法可以支持它呢?你会希望自动收到类似的警告。

答案 1 :(得分:1)

编写适配器

使用adapter pattern,您可以包装您不喜欢的MyClass的不可修改的API,并为消费者提供更清晰的API。您无法修改库,因此适配器将存在于您的代码库中。

当我doSomethingStraightForward时,我并不关心如何调用MyClass的复杂细节,我只想在其上设置Interface。可以隐藏与MyClass进行真实互动的担忧。

例如:

void doSomethingStraightForward() {
    Interface ifc = someCallToGetAnImpl();
    MyClassInterfaceAdapter myClassAdapter = new MyClassInterfaceAdapter();
    myClassAdapter.setInterface(ifc);
}    

示例实施

正如已经提到的使用反射你可以得到安装者。这是我尝试实现MyClassInterfaceAdapter时的一些代码。

public MyClassInterfaceAdapter() {
    this.myClass = new MyClass();
    this.interfaceSetters = getSetterMap(myClass);
}

public void setInterface(Interface iface) throws Exception {
    if (iface == null) {
        throw new IllegalArgumentException("iface must not be null");
    }

    Class<? extends Interface> ifaceClass = iface.getClass();

    if (!interfaceSetters.containsKey(ifaceClass)) {
        throw new IllegalArgumentException(String.format("iface type %s is not supported", ifaceClass.getName()));
    }

    Method ifaceSetter = interfaceSetters.get(ifaceClass);
    ifaceSetter.invoke(myClass, iface);
}

private static Map<Class<? extends Interface>, Method> getSetterMap(MyClass myClass) {
    Map<Class<? extends Interface>, Method> setterMap = new HashMap<>();

    Class<MyClass> myClassType = (Class<MyClass>) myClass.getClass();
    Class<Interface> interfaceType = Interface.class;

    for (Method method : myClassType.getDeclaredMethods()) {
        final String methodName = method.getName();

        if (methodName.startsWith(SetterPrefix)
                && method.getParameterCount() == 1
                && interfaceType.isAssignableFrom(method.getParameterTypes()[0])) {
            try {
                getMatchingMyEnum(method);
                setterMap.put((Class<? extends Interface>) method.getParameterTypes()[0], method);
            } catch (Exception e) {
                logger.log(Level.WARNING, "Setter not compatible: " + methodName, e);
            }
        }
    }

    return setterMap;
}

private static MyEnum getMatchingMyEnum(Method m) {
    int MyEnumStartIndex = SetterPrefix.length();
    String enumName = m.getName().substring(MyEnumStartIndex);
    return Enum.valueOf(MyEnum.class, enumName);
}

生产准备

在生产中使用它时,我会想到以下几点:

  • 您需要非常高的测试覆盖率,包括单元,集成和E2E测试。测试应涵盖快乐路径和失败场景。
  • 设置Interface失败时,您需要考虑要执行的操作。 throws Exception到处都不是最佳做法。
  • 您可能希望通过工厂或IoC容器管理MyClass及其适配器的创建,以使代码更易于单元测试(与上面的操作类似,转移创建对象及其依赖关系的问题)在其他地方)。

答案 2 :(得分:0)

如果您的呼叫者工作在setInterface(Interface)级别,而不关心/知道哪个类实际上正在实现该接口,请不要将其作为问题。实施Enum方法。

不知道为什么你想要/需要ifc.getClass()Class<?>不够好吗?如果子类化很深,它将返回Enum ClassA对象,尽管是叶类。

要确保在ClassA或其中一个子类通过时选择package test; interface Interface { /*content not included*/ } class ClassA implements Interface { /*content not included*/ } class ClassB implements Interface { /*content not included*/ } class ClassC implements Interface { /*content not included*/ } class MyClass { public enum MyEnum { ClassA(test.ClassA.class), ClassB(test.ClassB.class), ClassC(test.ClassC.class); private final Class<? extends Interface> impl; private MyEnum(Class<? extends Interface> impl) { this.impl = impl; } Class<? extends Interface> getImpl() { return this.impl; } } private MyEnum myEnum; private Interface ifc; public void setInterface(Interface ifc) { for (MyEnum e : MyEnum.values()) { if (e.getImpl().isAssignableFrom(ifc.getClass())) { this.myEnum = e; this.ifc = ifc; return; } } throw new IllegalArgumentException("Interface class is unknown: " + ifc.getClass().getName()); } } ,请执行以下操作。

Interface ifc = someCallToGetAnImpl();
MyClass myClass = new MyClass(); 
myClass.setInterface(ifc);

现在来电者很容易:

class ClassB extends ClassA

注意:如果您的类是枚举为彼此的子类,请确保首先列出子类。例如。如果enum ClassB,则必须在enum ClassA之前列出Transform controls

答案 3 :(得分:-2)

使用反射无法在Enum类中动态添加元素。 可能您可以使用动态代码来调用该方法。没有办法改变enumclass。

还有另一种实现方法,您可以拥有一个xml / properties文件,您可以按https://dzone.com/articles/enum-tricks-dynamic-enums

中的说明动态配置枚举类。