我正在使用一个我无法修改的库,它有一个包含以下 /**
* 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
应让软件保持动态,但看起来很糟糕。
欢迎任何干净,生产质量的建议或方法。
答案 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);
}
在生产中使用它时,我会想到以下几点:
Interface
失败时,您需要考虑要执行的操作。 throws Exception
到处都不是最佳做法。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
中的说明动态配置枚举类。