使用反射动态调用正确的 setter 方法
Dynamically call correct correct setter method with Reflection
我正在使用一个我无法修改的库,它有一个 class 以及以下 Enum
和 setters.
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
名称是如何与实现 Interface
的相应 class 名称匹配的字符串文字,以及每个 class 名称如何具有自己特定的 setter.
正如您想象的那样,调用它的代码非常简单:
Interface ifc = someCallToGetAnImpl();
MyClass myClass = new MyClass();
myClass.set???(ifc);
很多 Interface
的实现,我不能保证库的未来版本不会添加更多。所以我希望创建一个可以动态派生和调用正确 setter.
的函数
当然,我可以构建一个大的旧 if... else if...
块,但是在构建 Interface
的新实现时,这将需要更改软件。我还考虑过使用 Class.getDeclaredMethod(String name, Class<?>... parameterTypes)
来构建 name
参数,例如
"set" + ifc.getClass.getSimpleName()...
应该 保持软件动态,但看起来很笨拙。
欢迎任何干净的、生产质量的建议或方法。
您正在考虑的方法,通过 Class.getDeclaredMethod(String name, Class... parameterTypes) 等方法使用反射是解决您所描述问题的正确方法。这是一个笨拙的问题,值得采用笨拙的方法。
不过,我要补充的一件事是,您编写单元测试来验证您对 class 中的模式的假设,这样如果他们添加了一个不遵循该模式的方法,您得到提醒。例如,如果添加了一个新的枚举值,但没有方法支持它怎么办?你会希望自动提醒类似的事情。
无法使用反射在 Enum class 中动态添加元素。
可能您可以使用动态代码来调用该方法。没有办法改变 enumclass.
还有另一种实现方式,您可以拥有一个 xml/properties 文件,您可以在其中动态配置枚举 class,如 https://dzone.com/articles/enum-tricks-dynamic-enums
中所述
如果您的调用者在 Interface
级别工作,而没有 caring/knowing 而 class 实际上正在实现该接口,请不要让这成为他们的问题。实施 setInterface(Interface)
方法。
不知道你为什么 want/need Enum
。 ifc.getClass()
还不够好吗?如果 subclassing 很深,它将 return Class<?>
对象,尽管是叶子 class。
为了确保您 select Enum ClassA
每当 ClassA
或其中一个子 class 被传递时,请执行以下操作。
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 的枚举,它们是彼此的子class,请确保子class被列在第一位。例如。如果 class ClassB extends ClassA
,则 enum ClassB
必须列在 enum ClassA
.
之前
写一个适配器
使用 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
到处都不是最佳做法。
- 您可能希望通过工厂或 IoC 容器管理
MyClass
及其适配器的创建,以使代码更易于单元测试(类似于我们上面所做的概念,移动创建对象的关注点和他们在其他地方的依赖关系)。
我正在使用一个我无法修改的库,它有一个 class 以及以下 Enum
和 setters.
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
名称是如何与实现 Interface
的相应 class 名称匹配的字符串文字,以及每个 class 名称如何具有自己特定的 setter.
正如您想象的那样,调用它的代码非常简单:
Interface ifc = someCallToGetAnImpl();
MyClass myClass = new MyClass();
myClass.set???(ifc);
很多 Interface
的实现,我不能保证库的未来版本不会添加更多。所以我希望创建一个可以动态派生和调用正确 setter.
当然,我可以构建一个大的旧 if... else if...
块,但是在构建 Interface
的新实现时,这将需要更改软件。我还考虑过使用 Class.getDeclaredMethod(String name, Class<?>... parameterTypes)
来构建 name
参数,例如
"set" + ifc.getClass.getSimpleName()...
应该 保持软件动态,但看起来很笨拙。
欢迎任何干净的、生产质量的建议或方法。
您正在考虑的方法,通过 Class.getDeclaredMethod(String name, Class... parameterTypes) 等方法使用反射是解决您所描述问题的正确方法。这是一个笨拙的问题,值得采用笨拙的方法。
不过,我要补充的一件事是,您编写单元测试来验证您对 class 中的模式的假设,这样如果他们添加了一个不遵循该模式的方法,您得到提醒。例如,如果添加了一个新的枚举值,但没有方法支持它怎么办?你会希望自动提醒类似的事情。
无法使用反射在 Enum class 中动态添加元素。 可能您可以使用动态代码来调用该方法。没有办法改变 enumclass.
还有另一种实现方式,您可以拥有一个 xml/properties 文件,您可以在其中动态配置枚举 class,如 https://dzone.com/articles/enum-tricks-dynamic-enums
中所述如果您的调用者在 Interface
级别工作,而没有 caring/knowing 而 class 实际上正在实现该接口,请不要让这成为他们的问题。实施 setInterface(Interface)
方法。
不知道你为什么 want/need Enum
。 ifc.getClass()
还不够好吗?如果 subclassing 很深,它将 return Class<?>
对象,尽管是叶子 class。
为了确保您 select Enum ClassA
每当 ClassA
或其中一个子 class 被传递时,请执行以下操作。
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 的枚举,它们是彼此的子class,请确保子class被列在第一位。例如。如果 class ClassB extends ClassA
,则 enum ClassB
必须列在 enum ClassA
.
写一个适配器
使用 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
到处都不是最佳做法。 - 您可能希望通过工厂或 IoC 容器管理
MyClass
及其适配器的创建,以使代码更易于单元测试(类似于我们上面所做的概念,移动创建对象的关注点和他们在其他地方的依赖关系)。