这是我第二次发现自己编写这种代码,并决定必须有一种更易读的方法来实现这一目标:
我的代码试图找出一些东西,这些内容并没有很好地定义,或者有很多方法可以实现它。我希望我的代码尝试几种方法来解决它,直到它成功,或者它用完了策略。但我还没有办法让这个整洁可读。
我的特殊情况:我需要从界面中找到特定类型的方法。它可以注释为显式,但它也可以是唯一合适的方法(根据其参数)。
所以,我的代码目前是这样的:
Method candidateMethod = getMethodByAnnotation(clazz);
if (candidateMethod == null) {
candidateMethod = getMethodByBeingOnlyMethod(clazz);
}
if (candidateMethod == null) {
candidateMethod = getMethodByBeingOnlySuitableMethod(clazz);
}
if (candidateMethod == null) {
throw new NoSuitableMethodFoundException(clazz);
}
必须是更好的方式......
编辑:如果找到方法,则返回方法,否则返回null
。我可以将其切换为try / catch逻辑,但这几乎不会使它更具可读性。
Edit2:不幸的是,我只能接受一个答案:(
答案 0 :(得分:6)
对我而言,这是可读和可理解的。我只是将代码的丑陋部分提取到一个单独的方法(遵循“Robert C.Martin:Clean Code”中的一些基本原则)并添加一些javadoc(并在必要时道歉):
//...
try {
Method method = MethodFinder.findMethodIn(clazz);
catch (NoSuitableMethodException oops) {
// handle exception
}
以及稍后在MethodFinder.java
/**
* Will find the most suitable method in the given class or throw an exception if
* no such method exists (...)
*/
public static Method findMethodIn(Class<?> clazz) throws NoSuitableMethodException {
// all your effort to get a method is hidden here,
// protected with unit tests and no need for anyone to read it
// in order to understand the 'main' part of the algorithm.
}
答案 1 :(得分:4)
我认为对于一小部分方法你正在做的事情很好。
对于一个更大的集合,我可能倾向于构建一个Chain of Responsibility,它捕获了尝试一系列事物直到一个工作的基本概念。
答案 2 :(得分:2)
我认为这不是一种糟糕的做法。它有点冗长,但它清楚地传达了你正在做的事情,而且很容易改变。
但是,如果你想使它更简洁,你可以将方法getMethod*
包装到一个实现接口(“IMethodFinder”)或类似的类中:
public interface IMethodFinder{
public Method findMethod(...);
}
然后你可以创建你的类的实例,将它们放入一个集合并循环它:
...
Method candidateMethod;
findLoop:
for (IMethodFinder mf: myMethodFinders){
candidateMethod = mf.findMethod(clazz);
if (candidateMethod!=null){
break findLoop;
}
}
if (candidateMethod!=null){
// method found
} else {
// not found :-(
}
虽然可能有点复杂,但如果你这样做会更容易处理需要在调用findMethods *方法之间做更多工作(例如更多验证方法是否合适),或者在运行时是否可以配置查找方法的方法列表......
尽管如此,你的方法也可以。
答案 3 :(得分:2)
我很遗憾地说,但你使用的方法似乎是广为接受的方法。我在Spring,Maven等大型库的代码库中看到了很多这样的代码。
然而,另一种方法是引入一个可以从给定输入转换为给定输出的辅助接口。像这样:
public interface Converter<I, O> {
boolean canConvert(I input);
O convert(I input);
}
和辅助方法
public static <I, O> O getDataFromConverters(
final I input,
final Converter<I, O>... converters
){
O result = null;
for(final Converter<I, O> converter : converters){
if(converter.canConvert(input)){
result = converter.convert(input);
break;
}
}
return result;
}
那么你可以编写实现逻辑的可重用转换器。每个转换器都必须实现canConvert(input)
方法来决定是否使用它的转换例程。
实际上:您的请求提醒我的是Prototype(Javascript)中的Try.these(a,b,c)方法。
您案例的使用示例:
假设您有一些具有验证方法的bean。有几种策略可以找到这些验证方法。首先,我们将检查该注释是否出现在类型上:
// retention, target etc. stripped
public @interface ValidationMethod {
String value();
}
然后我们将检查是否存在名为“validate”的方法。为了简化操作,我假设所有方法都定义了Object类型的单个参数。您可以选择不同的模式。无论如何,这是示例代码:
// converter using the annotation
public static final class ValidationMethodAnnotationConverter implements
Converter<Class<?>, Method>{
@Override
public boolean canConvert(final Class<?> input){
return input.isAnnotationPresent(ValidationMethod.class);
}
@Override
public Method convert(final Class<?> input){
final String methodName =
input.getAnnotation(ValidationMethod.class).value();
try{
return input.getDeclaredMethod(methodName, Object.class);
} catch(final Exception e){
throw new IllegalStateException(e);
}
}
}
// converter using the method name convention
public static class MethodNameConventionConverter implements
Converter<Class<?>, Method>{
private static final String METHOD_NAME = "validate";
@Override
public boolean canConvert(final Class<?> input){
return findMethod(input) != null;
}
private Method findMethod(final Class<?> input){
try{
return input.getDeclaredMethod(METHOD_NAME, Object.class);
} catch(final SecurityException e){
throw new IllegalStateException(e);
} catch(final NoSuchMethodException e){
return null;
}
}
@Override
public Method convert(final Class<?> input){
return findMethod(input);
}
}
// find the validation method on a class using the two above converters
public static Method findValidationMethod(final Class<?> beanClass){
return getDataFromConverters(beanClass,
new ValidationMethodAnnotationConverter(),
new MethodNameConventionConverter()
);
}
// example bean class with validation method found by annotation
@ValidationMethod("doValidate")
public class BeanA{
public void doValidate(final Object input){
}
}
// example bean class with validation method found by convention
public class BeanB{
public void validate(final Object input){
}
}
答案 4 :(得分:1)
...我可以将其切换为try / catch逻辑,但这几乎不会使它更具可读性。
更改get ...方法的签名以便使用try / catch这将是一个非常糟糕的主意。例外是昂贵的,只应用于“特殊”条件。正如你所说,代码的可读性会降低。
答案 5 :(得分:1)
您可以使用Decorator Design Pattern来找到找到某些内容的不同方法。
public interface FindMethod
{
public Method get(Class clazz);
}
public class FindMethodByAnnotation implements FindMethod
{
private final FindMethod findMethod;
public FindMethodByAnnotation(FindMethod findMethod)
{
this.findMethod = findMethod;
}
private Method findByAnnotation(Class clazz)
{
return getMethodByAnnotation(clazz);
}
public Method get(Class clazz)
{
Method r = null == findMethod ? null : findMethod.get(clazz);
return r == null ? findByAnnotation(clazz) : r;
}
}
public class FindMethodByOnlyMethod implements FindMethod
{
private final FindMethod findMethod;
public FindMethodByOnlyMethod(FindMethod findMethod)
{
this.findMethod = findMethod;
}
private Method findByOnlyMethod(Class clazz)
{
return getMethodOnlyMethod(clazz);
}
public Method get(Class clazz)
{
Method r = null == findMethod ? null : findMethod.get(clazz);
return r == null ? findByOnlyMethod(clazz) : r;
}
}
用法非常简单
FindMethod finder = new FindMethodByOnlyMethod(new FindMethodByAnnotation(null));
finder.get(clazz);
答案 6 :(得分:0)
困扰你的是用于流控制的重复模式 - 它应该打扰你 - 但是在Java中没有太多的事情要做。
我对重复的代码&amp;像这样的模式,所以对我来说,提取重复的副本可能是值得的。粘贴控制代码并将其放入自己的方法中:
public Method findMethod(Class clazz)
int i=0;
Method candidateMethod = null;
while(candidateMethod == null) {
switch(i++) {
case 0:
candidateMethod = getMethodByAnnotation(clazz);
break;
case 1:
candidateMethod = getMethodByBeingOnlyMethod(clazz);
break;
case 2:
candidateMethod = getMethodByBeingOnlySuitableMethod(clazz);
break;
default:
throw new NoSuitableMethodFoundException(clazz);
}
return clazz;
}
其缺点是非常规且可能更冗长,但由于“肉”中的杂乱程度较低,因此没有那么多重复代码(较少错别字)和读取的优点。
此外,一旦逻辑被提取到它自己的类中,详细信息根本不重要,它的读取/编辑清晰,对我而言,这给了(一旦你理解了while循环正在做什么)
我确实有这种令人讨厌的愿望:
case 0: candidateMethod = getMethodByAnnotation(clazz); break;
case 1: candidateMethod = getMethodByBeingOnlyMethod(clazz); break;
case 2: candidateMethod = getMethodByBeingOnlySuitableMethod(clazz); break;
default: throw new NoSuitableMethodFoundException(clazz);
要突出显示实际上正在进行的操作(按顺序),但在Java中这是完全不可接受的 - 您实际上会发现它在其他语言中是常见的或首选的。
PS。在groovy中,这将是彻头彻尾的优雅(该死的我讨厌那个词):
actualMethod = getMethodByAnnotation(clazz) ?:
getMethodByBeingOnlyMethod(clazz) ?:
getMethodByBeingOnlySuitableMethod(clazz) ?:
throw new NoSuitableMethodFoundException(clazz) ;
elvis运营商规则。注意,最后一行可能实际上不起作用,但如果没有,那将是一个微不足道的补丁。