2010-11-02 9 views
6

Ésta es la segunda vez que me encontré escribiendo este tipo de código, y decidí que debe haber una manera más fácil de leer para lograr esto:Dejar que el código de probar cosas diferentes hasta que tenga éxito, perfectamente

Mi código intenta descubrir algo, eso no está exactamente bien definido, o hay muchas maneras de lograrlo. Quiero que mi código pruebe varias formas de resolverlo, hasta que tenga éxito, o se quede sin estrategias. Pero no he encontrado una manera de hacer esto limpio y legible.

Mi caso particular: Necesito encontrar un tipo particular de método desde una interfaz. Puede anotarse para explicitud, pero también puede ser el único método adecuado (según sus argumentos).

Por lo tanto, mi código lee actualmente, así:

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

Hay debe haber una mejor manera ...

Editar: Los métodos de devolución de un método si lo encuentra, null lo contrario. Podría cambiar eso para intentar/capturar la lógica, pero eso apenas lo hace más legible.

Edit2: Por desgracia, sólo puede aceptar una respuesta :(

+0

es probable que desee un lenguaje dinámico, p. Python, Ruby; donde estos modismos se vuelven más naturales y fáciles de usar debido a tener funciones y clases como objetos de primera clase (por lo tanto, puede poner esas funciones en una lista y utiliza un ciclo para llamar a las funciones una a una). –

Respuesta

6

Para mí es legible y comprensible. Simplemente extraería la parte fea del código a un método diferente (siguiendo algunos principios básicos de "Robert C. Martin: código limpio") y agregaría algunos javadoc (y disculpas, si es necesario) así:

//... 
try { 
    Method method = MethodFinder.findMethodIn(clazz); 
catch (NoSuitableMethodException oops) { 
    // handle exception 
} 

y más tarde en 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

... Podría cambiar eso para tratar/lógica de captura, pero eso no hace que sea más fácil de leer

.

Cambiar la firma de los métodos get ... para que pueda usar try/catch sería una muy mala idea. Las excepciones son caras y solo deberían usarse para condiciones "excepcionales". Y como usted dice, el código sería menos legible.

2

No creo que esta sea una mala manera de hacerlo. Es un poco detallado, pero claramente transmite lo que estás haciendo, y es fácil de cambiar.

embargo, si usted quiere que sea más concisa, se puede envolver los métodos getMethod* en una clase que implementa una interfaz ("IMethodFinder") o similar:

public interface IMethodFinder{ 
    public Method findMethod(...); 
} 

continuación, puede crear instancias de usted clase, ponerlos en una colección y lazo sobre él:

... 
Method candidateMethod; 
findLoop: 
for (IMethodFinder mf: myMethodFinders){ 
    candidateMethod = mf.findMethod(clazz); 
    if (candidateMethod!=null){ 
    break findLoop; 
    } 
} 

if (candidateMethod!=null){ 
    // method found 
} else { 
    // not found :-(
} 

Si bien podría decirse algo más complicado, esto será más fácil de manejar si por ejemplo necesita hacer más trabajo entre llamar a los métodos findMethods * (como más verificación de que el método es apropiado), o si la lista de formas de encontrar métodos es configurable en tiempo de ejecución ...

Aún así, su enfoque probablemente sea correcto también.

+0

Si bien esta forma probablemente oscurece lo que realmente se hace (debido a myMethodFinders), lo votaré mejor, ya que será más fácil de leer si hay muchos de estos buscadores. –

+0

Esto es básicamente lo mismo que mi solución, solo que este enfoque se basa en devolver valores nulos, mientras que mi enfoque puede decidir de antemano si puede encontrar/convertir algo o no (mi enfoque es, por supuesto, más caro). –

+0

@Henrik Paul: Bueno, es cierto que oculta los detalles de la implementación. Por lo tanto, se "esconde" en cierto sentido, pero de una manera que generalmente se considera algo bueno (porque está ocultando detalles de implementación). Aún así, por supuesto, la mejor solución dependerá de lo que se necesite en una aplicación. – sleske

2

Lamento decirlo, pero el método que utiliza parece ser el ampliamente aceptado. Veo muchos códigos como ese en la base de códigos de grandes bibliotecas como Spring, Maven, etc.

Sin embargo, una alternativa sería introducir una interfaz auxiliar que pueda convertir una entrada determinada en una salida determinada.Algo como esto:

public interface Converter<I, O> { 
    boolean canConvert(I input); 
    O convert(I input); 
} 

y un método de ayuda

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; 
} 

Entonces se podría escribir convertidores reutilizables que implementan la lógica. Cada uno de los convertidores tendría que implementar el método canConvert(input) para decidir si se utilizarán las rutinas de conversión.

En realidad: lo que me recuerda su solicitud es el método Try.these(a,b,c) en Prototype (Javascript).


Ejemplo de uso para su caso:

Digamos que usted tiene un poco de frijoles que tienen métodos de validación. Hay varias estrategias para encontrar estos métodos de validación. En primer lugar comprobaremos si esta anotación está presente en el tipo:

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

Entonces comprobaremos si hay un método llamado "validación". Para facilitar las cosas, supongo que todos los métodos definen un solo parámetro de tipo Object. Puedes elegir un patrón diferente. De todos modos, aquí está el código de ejemplo:

// 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){ 
    } 

} 
+0

¿Podría explicarnos un poco sobre esta estrategia de conversión? ¿Cómo se aplicaría esto a mi problema en la práctica? –

+0

seguro, ver mi actualización –

+0

Una solución interesante. Sin embargo, parece un poco * demasiado * general para este problema, pero me doy cuenta de que esto es discutible. Siempre hay una línea fina entre muy poca y demasiada capacidad de expansión. – sleske

1

Es posible utilizar Decorator Design Pattern para llevar a cabo diferentes formas de encontrar la manera de encontrar algo.

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; 
    } 
} 

El uso es bastante simple

FindMethod finder = new FindMethodByOnlyMethod(new FindMethodByAnnotation(null)); 
finder.get(clazz); 
+0

seguro, eso funciona, pero me parece un uso muy incómodo del patrón decorador. Decorator es generalmente para agregar cosas a una solución existente, no para agregar una solución por separado. –

+0

bien, imagine tener una tercera solución que quiera agregar. El patrón de decorador colocado en su lugar será la forma más fácil de extender el código sin tocar las soluciones agregadas previamente. –

+0

Es cierto. No estoy diciendo que esto sea malo o que no funcione, solo creo que es un uso contraintuitivo del patrón de decorador. –

4

Creo que para un pequeño conjunto de métodos de lo que estás haciendo está bien.

Para un conjunto más grande, podría estar inclinado a construir un Chain of Responsibility, que captura el concepto básico de probar una secuencia de cosas hasta que funcione.

0

Lo que le molesta es el patrón repetitivo utilizado para el control de flujo, y debería molestarlo, pero no hay mucho que hacer en Java.

me pongo muy molesto por código repetido & patrones como este, así que para mí, probablemente sería la pena para extraer la copia & código de control pasta repetida y ponerlo en su propio método:

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; 
} 

Qué tiene la desventaja de ser poco convencional y posiblemente más prolijo, pero la ventaja de no tener tantos códigos repetidos (menos errores tipográficos) y de leer es más fácil debido a que hay un poco menos de desorden en la "Carne".

Además, una vez que la lógica ha sido extraído en su propia clase, verbosa no importa en absoluto, es la claridad de lectura/edición y para mí esto da que (una vez que entienda lo que el bucle while está haciendo)

tengo este deseo desagradable para hacer esto:

case 0: candidateMethod = getMethodByAnnotation(clazz);    break; 
case 1: candidateMethod = getMethodByBeingOnlyMethod(clazz);   break; 
case 2: candidateMethod = getMethodByBeingOnlySuitableMethod(clazz); break; 
default: throw new NoSuitableMethodFoundException(clazz); 

para resaltar lo que en realidad se está haciendo (en orden), pero en Java esto es completamente inaceptable - you'd realidad encontrarlo ordinarias o preferidas de alguna otros idiomas.

PS. Esto sería francamente elegante (maldito odio esa palabra) en el maravilloso:

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

Las reglas del operador de Elvis. Tenga en cuenta que la última línea puede no funcionar en realidad, pero sería un parche trivial si no lo hace.

Cuestiones relacionadas