6

这是我第二次发现自己编写这种代码,并决定必须有一种更易读的方法来完成它:

我的代码试图找出一些东西,但定义不完全,或者有很多方法可以完成它。我希望我的代码尝试几种方法来解决它,直到它成功,或者它用完了策略。但是我还没有找到一种方法来使这个整洁和可读。

我的特殊情况:我需要从接口中找到一种特定类型的方法。它可以被注释为明确的,但它也可以是唯一合适的方法(根据它的参数)。

所以,我的代码目前如下所示:

Method candidateMethod = getMethodByAnnotation(clazz);
if (candidateMethod == null) {
  candidateMethod = getMethodByBeingOnlyMethod(clazz);
}
if (candidateMethod == null) {
  candidateMethod = getMethodByBeingOnlySuitableMethod(clazz);
}
if (candidateMethod == null) {
  throw new NoSuitableMethodFoundException(clazz);
}

一定有更好的办法……</p>

编辑:如果找到,则方法返回一个方法,null否则返回。我可以将其切换为 try/catch 逻辑,但这几乎不会使其更具可读性。

Edit2:不幸的是,我只能接受一个答案:(

4

7 回答 7

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.
}
于 2010-11-02T08:40:17.767 回答
4

我认为对于一小部分方法,您正在做的事情很好。

对于更大的集合,我可能倾向于构建一个责任链,它捕获了尝试一系列事情直到一个工作成功的基本概念。

于 2010-11-02T11:22:41.200 回答
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* 方法之间做更多工作(例如更多验证该方法是否合适),或者如果查找方法的方法列表可配置为运行...

不过,您的方法可能也可以。

于 2010-11-02T08:08:55.267 回答
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){
    }

}
于 2010-11-02T08:09:01.717 回答
1

...我可以将其切换为 try/catch 逻辑,但这几乎不会使其更具可读性。

更改 get... 方法的签名以便您可以使用 try / catch 将是一个非常糟糕的主意。异常是昂贵的,应该只用于“异常”条件。正如您所说,代码的可读性会降低。

于 2010-11-02T08:00:44.497 回答
1

您可以使用装饰器设计模式来完成找出如何找到某物的不同方法。

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);
于 2010-11-02T08:10:07.910 回答
0

困扰您的是用于流控制的重复模式——它应该困扰您——但在 Java 中没有太多需要做的事情。

我对这样的重复代码和模式感到非常恼火,所以对我来说,提取重复的复制和粘贴控制代码并将其放入自己的方法中可能是值得的:

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 运算符规则。请注意,最后一行可能实际上不起作用,但如果它不起作用,那将是一个微不足道的补丁。

于 2010-11-02T23:25:10.307 回答