2011-01-26 5 views
7

Sto scrivendo un metodo in Java:SECCO: Ridurre al minimo il codice ripetuto in Java

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

mi piacerebbe scrivere un secondo metodo con esattamente la stessa logica, ma un diverso tipo di ritorno:

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

Sto cercando di capire un modo non-hackish per ridurre al minimo la quantità di codice ripetuto tra i due metodi. L'unica differenza logica tra i due è che, quando si aggiunge un oggetto alla lista che restituito, il primo metodo aggiunge l'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; 
} 

e il secondo aggiunge una String rappresentazione del Foo:

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

In realtà, non è proprio quello semplice. Non voglio aggiungere uno a toReturn a meno che non sia assolutamente sicuro che appartenga a questo. Di conseguenza, tale decisione viene presa per- foo utilizzando le funzioni di supporto. Con due diverse versioni dei metodi, avrei bisogno di diverse versioni delle funzioni di supporto - alla fine, scriverò due serie di metodi quasi identici, ma per un piccolo tipo generico.


Posso scrivere un singolo metodo che contiene tutta la logica decisionale, ma in grado di generare sia un List<Foo> o un List<String>? È possibile farlo senza utilizzare i tipi di tipo List non elaborati (cattiva pratica in terreni generici!) O caratteri jolly List<?>? Immagino un'implementazione che sembra qualcosa di simile:

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 */ 
} 

C'è, modo elegante bello fare questo? Ho giocato con metodi generici, ma non riesco a vedere un modo per farlo. Anche il vagare con la riflessione non mi ha portato da nessuna parte (forse ho bisogno di qualcosa come TypeToken? ... eww).

+0

Potreste essere interessati a questo nuovo sito StackExchange: http://codereview.stackexchange.com/ – Mchl

+0

@Mchl: ** gah * * - ora è lo –

risposta

7

Non riesci a esternare logica di trasformazione in una strategia separata (come Function<F, T> di Guava):

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

Questo sembra decisamente promettente. Lasciami giocare con questo. –

+0

Brillante! Grazie. Stavo già usando Guava. Ho bisogno di scavare più a fondo in esso. –

0

Ho un Interfaccia "SearchFilter" e una classe astratta "FilterAdapter" che utilizzo in modo simile a questo. La logica decisionale può essere fatta indipendentemente e in modo generico aggiungendo effettivamente le cose alla lista di ritorno. Vorrei controllare ogni Foo e dire "vero" includerlo o "falso" escluderlo.

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

Un filtro può essere applicato a una raccolta esistente con il metodo apply(), restituendo una nuova collezione che include solo gli elementi desiderati.

newCollection = myfilter.apply(originalItems); 

Questo non può essere utile a voi, ma il concetto include() dovrebbe funzionare bene per evitare di ripetere la logica decisione.

Si potrebbe avere un FooFilter extends FilterAdapter<Foo> (ho anche istanziare questi anonima in linea a volte), che fornisce un'implementazione di 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(); 
    } 
} 

Il metodo apply() è quasi sempre e solo un loop all'interno di raccolta iniziale e la prova include così ha un'implementazione predefinita nel mio FilterAdapter ma può essere sovrascritta.

0

E 'un po' brutto, ma penso che questo potrebbe funzionare:

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