2011-01-26 21 views
7

Estoy escribiendo un método en Java:SECO: Minimizar el código repetido en Java

List<Foo> computeFooList(/* arguments */) 
{ 
    /* snip */ 
} 

me gustaría escribir un segundo método con exactamente la misma lógica, pero un diferente tipo de retorno:

List<String> computeStringList(/* same arguments */) 
{ 
    /* snip */ 
} 

Estoy tratando de encontrar una forma no hackish de minimizar la cantidad de código repetido entre los dos métodos. El solamente diferencia lógica entre los dos es que, cuando la adición de un objeto a la lista que se devuelve, el primer método agrega el acutal Foo:

List<Foo> computeFooList(/* arguments */) 
{ 
    List<Foo> toReturn = ... 
    ... 
    for (Foo foo : /* some other list of Foo */) 
    { 
     if (/* some condition */) 
     { 
      toReturn.add(foo); 
     } 
    } 
    ... 
    return toReturn; 
} 

y la segunda añade una representación String de la Foo:

List<String> computeStringList(/* same arguments */) 
{ 
    List<String> toReturn = ... 
    ... 
    for (Foo foo : /* some other list of Foo */) 
    { 
     if (/* some condition */) 
     { 
      toReturn.add(foo.toString()); 
     } 
    } 
    ... 
} 

En realidad, no es exactamente que simple. No quiero agregar un Foo a toReturn a menos que esté absolutamente seguro de que pertenece allí. Como resultado, esa decisión se toma por- foo usando funciones auxiliares. Con dos versiones diferentes de los métodos, necesitaría también diferentes versiones de las funciones de ayuda; al final, estaría escribiendo dos conjuntos de métodos casi idénticos, pero para un pequeño tipo genérico.


¿Puedo escribir un método único que contiene toda la lógica de toma de decisiones, pero puede generar ya sea un List<Foo> o una List<String>? ¿Es posible hacer esto sin usar tipos sin procesar List (mala práctica en genéricos terrestres) o comodines List<?>? Imagino una implementación que se ve algo como esto:

List<Foo> computeFooList(/* args */) 
{ 
    return computeEitherList(/* args */, Foo.class); 
} 

List<String> computeStringList(/* args */) 
{ 
    return computeEitherList(/* args */, String.class); 
} 

private List<???> computeEitherList(/* args */, Class<?> whichType) 
{ 
    /* snip */ 
} 

¿Hay alguna manera agradable, elegante de hacer esto? He estado jugando con métodos genéricos, pero no veo la manera de hacerlo. Incluso andar sucio con la reflexión no me ha llevado a ninguna parte (tal vez necesito algo como TypeToken? ... eww).

+0

que podría estar interesado en este nuevo sitio StackExchange: http://codereview.stackexchange.com/ – Mchl

+0

@Mchl: ** * Gah * - ahora está en –

Respuesta

7

¿No puedes exteriorizar lógica de transformación en una estrategia independiente (como Function<F, T> de guayaba):

public <T> List<T> computeList(/* arguments */, Function<? super Foo, T> f) { 
    List<T> toReturn = ...  ...  
    for (Foo foo : /* some other list of Foo */) { 
     if (/* some condition */) { 
      toReturn.add(f.apply(foo)); 
     } 
    } 
    return toReturn; 
} 

computeFooList:

computeList(..., Functions.identity()); 

computeStringList:

computeList(..., Functions.toStringFunction()); 
+0

Esto definitivamente parece prometedor. Déjame jugar con esto. –

+0

Brillante! Gracias. Yo ya estaba usando guayaba también. Necesito profundizar en eso. –

0

tengo una La interfaz "SearchFilter" y una clase abstracta "FilterAdapter" que uso de maneras similares a esto. La lógica de decisión se puede hacer de forma independiente y de forma genérica a partir de agregar elementos a la lista de resultados. Revisaría cada Foo y diría "verdadero" incluirlo o "falso" excluirlo.

public interface SearchFilter<T> 
{ 
    public boolean include(T item); 
    public Collection<T> apply(Collection<T> items); 
} 

Un filtro se puede aplicar a una colección existente con el método apply(), volviendo una nueva colección que sólo incluye los elementos deseados.

newCollection = myfilter.apply(originalItems); 

Esto puede no ser útil para usted, pero el concepto include() debería funcionar bien para evitar repetir el procedimiento de decisión.

Usted podría tener un FooFilter extends FilterAdapter<Foo> (también instanciar estos anónimamente en línea a veces) que proporciona una implementación de include

public FooFilter extends FilterAdapter<Foo> 
{ 
    public boolean include(Foo item) 
    { 
     if (item.isInvalid()) return false; 
     // or any other complex logic you want 
     return item.hasGoodThings(); 
    } 
} 

El método apply() es casi siempre solo bucle sobre la recogida y pruebas iniciales include por lo que tiene una implementación predeterminada en mi FilterAdapter pero se puede anular.

0

Es un poco feo, pero creo que esto podría funcionar:

List<Foo> computeFooList(/* args */) { 
    return computeEitherList(/* args */, Foo.class); 
} 

List<String> computeStringList(/* args */) { 
    return computeEitherList(/* args */, String.class); 
} 

private <T> List<T> computeEitherList(/* args */, Class<T> whichType) { 
    List<T> rval = new ArrayList<T>(); 
    for (Foo foo : listOfFoo) { 
     // process foo 

     if (whichType.equals(Foo.class)) { 
      rval.add(whichType.cast(foo)); 
     } 
     else if (whichType.equals(String.class)) { 
      rval.add(whichType.cast(foo.toString())); 
     } 
     else { 
      throw new SomeException("Cannot compute list for type " + whichType); 
     } 
    } 
    return rval; 
} 
Cuestiones relacionadas