Java Generics - come trovare un equilibrio tra espressività e semplicità

8

Sto sviluppando un codice che utilizza i farmaci generici e uno dei miei principi guida era renderlo utilizzabile per gli scenari futuri, e non solo per quelli di oggi. Tuttavia, diversi colleghi hanno dichiarato che avrei potuto scambiare la leggibilità per motivi di estensibilità. Volevo raccogliere un feedback sui possibili modi per risolvere questo problema.

Per essere precisi, ecco un'interfaccia che definisce una trasformazione: si inizia con una raccolta di elementi di origine e si applica la trasformazione a ciascun elemento, memorizzando i risultati in una raccolta di destinazione. Inoltre, voglio essere in grado di restituire la raccolta di destinazione al chiamante e, anziché costringerli a utilizzare un riferimento alla raccolta, voglio che siano in grado di utilizzare qualsiasi tipo di raccolta effettivamente fornito per la raccolta di destinazione.

Infine, ho reso possibile che il tipo di elementi nella collezione di destinazione sia diverso dal tipo di elementi nella raccolta di origine, perché forse è ciò che fa la trasformazione. Nel mio codice, ad esempio, diversi elementi di origine costituiscono un elemento di destinazione dopo la trasformazione.

Questo produce la seguente interfaccia:

interface Transform<Src, Dst> {
    <DstColl extends Collection<? super Dst>> DstColl transform(
                Collection<? extends Src> sourceCollection,
                DstColl                   destinationCollection);
}

Ho cercato di essere tutto gentile e applicare il principio PECS di Josh Bloch (produttore extends, consumer super) per assicurarmi che l'interfaccia sia utilizzabile con super- e sottotipi dove appropriato. Il risultato finale è in qualche modo una mostruosità.

Ora, sarebbe stato bello poter estendere questa interfaccia e specializzarla in qualche modo. Ad esempio, se non mi interessa davvero giocare bene con i sottotipi degli elementi sorgente e dei supertipi degli elementi di destinazione, potrei avere:

interface SimpleTransform<Src, Dst> {
    <DstColl extends Collection<Dst>> DstColl transform(
                  Collection<Src> sourceCollection,
                  DstColl         destinationCollection);
}

Ma non c'è modo di farlo in Java. Voglio rendere le implementazioni di questa interfaccia qualcosa che altri in realtà considererebbero di fare, invece di correre nella paura. Ho preso in considerazione diverse opzioni:

  • Non restituire la raccolta di destinazione. Sembra strano dato che fai una trasformazione ma non ottieni niente indietro.
  • Avere una classe astratta che implementa questa interfaccia, ma poi traduce i parametri in qualcosa di più facile da usare e chiama un altro metodo "translateImpl ()" che ha la firma più semplice e quindi presenta meno oneri cognitivi per gli implementatori. Ma è strano dover scrivere una classe astratta solo per rendere l'interfaccia user-friendly.
  • rinunciare all'estensibilità e basta avere l'interfaccia più semplice. Forse coppia che non restituisce la collezione di destinazione. Ma questo limita le mie opzioni in futuro.

Che ne pensi? Mi manca un approccio che potrei usare?

    
posta RuslanD 13.02.2013 - 00:29
fonte

3 risposte

2

Raccolte guava fornisce già questa funzionalità e se puoi resistere ancora un po ', Java 8 fornirà anche questo :-) . FWIW Penso che il tuo uso di ? extends T sia l'idioma corretto, non molto da fare dato che Java usa Type Erasure

    
risposta data 13.02.2013 - 01:00
fonte
0

Personalmente non vedo molti problemi con il modo in cui hai definito la tua interfaccia, tuttavia sono abituato a giocherellare con Generics e posso capire che i tuoi colleghi troveranno il tuo codice un po 'sdolcinato se non lo è il loro caso.

Se fossi in te, andrei con la terza soluzione che dici: rendi le cose più semplici a scapito di dover tornare indietro in seguito. Perché dopo tutto, forse non dovrai farlo alla fine; o finiresti per essere in grado di usarlo completamente a un certo punto, ma non abbastanza per compensare lo sforzo che hai fatto per rendere questa interfaccia così generica, così come lo sforzo che i tuoi colleghi potrebbero aver fatto per avvolgere la loro testa intorno esso.

Un'altra cosa da considerare è la frequenza con cui utilizzi questa interfaccia e a quale livello di complessità. Inoltre, quanto utile questa interfaccia si rivela nei tuoi progetti. Se consente un'elevata riusabilità di alcuni componenti (e non solo potenziali, ma reali), è una buona cosa. Se un'interfaccia molto più semplice funziona anche il 90% delle volte, potresti chiederti se per il restante 10% un'interfaccia complessa si dimostrerebbe utile o meno.

Non penso sarebbe una buona idea non usare super e extend , anche se se ne può fare a meno per il momento, sono certo che ai tuoi colleghi non dispiacerà vederli scomparire. Tuttavia, hai davvero tante situazioni in cui devi modificare anche il tipo di Collection ?

Alcuni consigli già forniti sono davvero buoni, e sono d'accordo con Frank sul seguire il principio YAGNI a meno che tu non abbia ottime ragioni per non farlo. È ancora possibile modificare il codice quando si presenterà la necessità di una maggiore complessità, ma non è di grande utilità sviluppare cose che non sono abbastanza sicure verranno presto utilizzate. Inoltre, il consiglio di Martijn di usare Guava è da considerare seriamente. Non ho ancora avuto l'opportunità di usarlo, ma ne ho sentito parlare molto bene, anche in una presentazione in cui è stato discusso il modello di Transformer (puoi guardarlo online su InfoQ se sei interessato).

Per inciso, non sarebbe meglio nell'interfaccia attuale avere destinationCollection essere di tipo Class<DstColl> ?

    
risposta data 13.02.2013 - 20:39
fonte
0

Mi piace racchiudere le raccolte Java. L'incapsulamento corretto può davvero aiutare. Questo modello deve essere correlato in base al tipo, ma lascia ogni riga di codice inconsapevole del tipo di raccolta.

Ad esempio, supponiamo che ci sia un elenco di prodotti. La classe del prodotto sarebbe immutabile e contiene alcuni metodi di utilità. La classe dell'elenco dei prodotti può aggiungere un prodotto o un'altra lista ad esso. Se si desidera eseguire un metodo sull'intero elenco, sarebbe nella classe dell'elenco dei prodotti. Esiste un metodo che accetta una classe di elenchi di filtri e fornisce un nuovo elenco di prodotti con quelli filtrati. Il filtro verrà eliminato dall'originale.

C'è un'interfaccia filtro che decide se UN prodotto passa il filtro. Ci sarebbe una classe elenco filtri che ha un elenco di filtri e implementa l'interfaccia richiedendo TUTTI i filtri per passare un prodotto affinché il prodotto passi.

La cosa buffa è che posso impilare una pila e cambiarla in una lista collegata senza cambiamenti evidenti come questa. Può fare il loop e fare condizionali molto evidenti e semplici. Quindi veste il codice imperativo e aggiunge flessibilità. Ed è ben orgonizzato.

    
risposta data 12.05.2016 - 23:38
fonte

Leggi altre domande sui tag