代码之家  ›  专栏  ›  技术社区  ›  Henrik Paul

让代码尝试不同的东西,直到成功为止

  •  6
  • Henrik Paul  · 技术社区  · 15 年前

    我的代码试图找出一些不完全定义好的东西,或者有很多方法可以完成它。我想让我的代码尝试几种方法来解决问题,直到成功,或者策略失效。但我还没有找到一种方法让这篇文章简洁易读。

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

    所以,我的代码现在读起来是这样的:

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

    必须 做一个更好的方式

    如果找到,方法将返回一个方法, null

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

    7 回复  |  直到 15 年前
        1
  •  6
  •   Andreas Dolk    15 年前

    对我来说,这是可读的和可以理解的。我只需将代码的丑陋部分提取到一个单独的方法中(遵循“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.
    }
    
        2
  •  4
  •   Don Roby    15 年前

    我认为对于一小部分方法来说你所做的是好的。

    对于更大的一组,我可能倾向于建立一个 Chain of Responsibility ,它捕获了尝试一系列事情直到成功的基本概念。

        3
  •  2
  •   sleske    15 年前

    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*方法之间做更多的工作(例如更多验证方法是否合适),或者如果查找方法的方法列表在运行时是可配置的,则这将更容易处理。。。

        4
  •  2
  •   Sean Patrick Floyd    15 年前

    我很抱歉,但你使用的方法似乎被广泛接受。在Spring、Maven等大型库的代码库中,我看到了很多这样的代码。

    但是,另一种方法是引入一个helper接口,该接口可以从给定的输入转换为给定的输出。像这样的:

    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) 方法来决定是否使用它的转换例程。

    事实上:你的要求让我想起了 Try.these(a,b,c) Prototype(Javascript)中的方法。


    假设有一些bean有验证方法。有几种策略可以找到这些验证方法。首先,我们将检查此批注是否存在于类型上:

    // retention, target etc. stripped
    public @interface ValidationMethod {
        String value();
    }
    

    // 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){
        }
    
    }
    
        5
  •  1
  •   Boris Pavlović    15 年前

    你可以用 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
  •  1
  •   Stephen C    15 年前

    ... 我可以将其转换为try/catch逻辑,但这很难使它更具可读性。

        7
  •  0
  •   Bill K    15 年前

    困扰您的是用于流控制的重复模式——它应该会困扰您——但是在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;
    }
    

    它的缺点是非常规,而且可能更加冗长,但是它的优点是没有那么多重复的代码(更少的拼写错误),而且由于“肉”中的混乱少了一点,所以读起来更容易。

    此外,一旦逻辑被提取到它自己的类中,verbose一点也不重要,它对于阅读/编辑来说是清晰的,对我来说,这就给了它(一旦你理解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中这是完全不可接受的——实际上您会发现它在其他一些语言中很常见或更受欢迎。

    另外,这在groovy中是非常优雅的(该死,我讨厌这个词):

    actualMethod = getMethodByAnnotation(clazz)                   ?:
                   getMethodByBeingOnlyMethod(clazz)              ?:
                   getMethodByBeingOnlySuitableMethod(clazz)      ?:
                   throw new NoSuitableMethodFoundException(clazz) ;
    

    推荐文章