【问题标题】:Letting the code try different things until it succeeds, neatly让代码尝试不同的事情,直到成功,整齐
【发布时间】:2010-11-02 07:42:05
【问题描述】:

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

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

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

所以,我的代码目前是这样的:

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: 不幸的是,我只能接受一个答案 :(

【问题讨论】:

  • 你可能想要一种动态语言,例如蟒蛇,红宝石;由于将函数和类作为第一类对象,这些习语变得更加自然和易于使用(因此,您可以将这些函数放入列表中并使用循环逐个调用函数)。

标签: java heuristics


【解决方案1】:

对我来说,它是可读且可以理解的。我只是将代码中丑陋的部分提取到一个单独的方法中(遵循“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】:

    我认为你正在做的一小部分方法是好的。

    对于更大的集合,我可能倾向于构建一个Chain of Responsibility,它抓住了尝试一系列事物直到一个成功的基本概念。

    【讨论】:

      【解决方案3】:

      我不认为这是一种糟糕的做法。它有点冗长,但它清楚地传达了你在做什么,并且很容易更改。

      不过,如果您想让它更简洁,您可以将方法 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* 方法之间做更多工作(例如更多验证该方法是否合适),或者查找方法的方法列表是否可在运行时配置...

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

      【讨论】:

      • 虽然这种方式可能会掩盖实际所做的事情(因为 myMethodFinders),但我会赞成这一点,因为如果有 许多 这些查找器,它会更具可读性.
      • 这和我的方案基本一样,只是这个方法依赖于返回空值,而我的方法可以提前决定它是否可以找到/转换某些东西(我的方法当然更多贵)。
      • @Henrik Paul:确实,它隐藏了实现的细节。所以它在某种意义上是“隐藏”的,但在某种程度上通常被认为是一件好事(因为它隐藏了实现细节)。当然,最好的解决方案当然取决于应用程序的需求。
      • @seanizer:是的,你的解决方案比我的更通用;但我的理解更容易一些,我相信:-)。无论如何,这两个答案都可以显示不同的解决方案。
      【解决方案4】:

      很抱歉,您使用的方法似乎已被广泛接受。我在 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();
      }
      

      然后我们将检查是否有一个名为“验证”的方法。为了使事情更容易,我假设所有方法都定义了一个 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){
          }
      
      }
      

      【讨论】:

      • 您能否详细说明一下这种转换策略 - 这在实践中如何适用于我的问题?
      • 一个有趣的解决方案。不过,对于这个问题来说,这似乎有点一般,但我意识到这是值得商榷的。可扩展性太少和太多之间总是有一条细线。
      • Nitpick:canConvert 对我来说似乎是多余的,我发现通过返回 null 来表示“没有结果”是完全可以接受的。此外,如果canConvert 内部无论如何都需要使用findMethod,那么您可能会因为总是调用findMethod 两次而引入性能问题...
      • 当然,canConvert() 方法是您的方法和我的方法之间的主要区别。基本上:在我的方法中,转换器/查找器可以说:'是的,我负责,但你得到的是空值'(无论这是一个好主意还是坏主意)。我当然同意性能问题,但在生产情况下,我会让助手将他们的方法查找缓存在地图中,这样可以将性能损失降至最低。
      【解决方案5】:

      您可以使用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】:

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

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

      【讨论】:

        【解决方案7】:

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

        猫王运营商规则。请注意,最后一行可能实际上不起作用,但如果它不起作用,那将是一个微不足道的补丁。

        【讨论】:

          猜你喜欢
          • 2015-01-03
          • 2016-02-16
          • 1970-01-01
          • 1970-01-01
          • 2023-03-17
          • 1970-01-01
          • 2021-06-02
          • 2014-04-21
          • 2019-02-23
          相关资源
          最近更新 更多