giovedì 23 aprile 2009

Diversi modi di correlare i dati in un contenitore

Con questo articolo voglio prendere in considerazione, da un punto di vista didattico, i diversi modi messi a disposizione da Java per correlare fra di loro i dati di una collezione.

Si consideri il caso di una serie di articoli da inserire in appositi scaffali. La programmazione OOP ci insegna che Articolo e Scaffale devono essere modellati come classi poiché sono astrazioni di dato. Ma come mettiamo in relazione le istanze delle due classi? Ovvero, come implementiamo il legame secondo il quale uno scaffale contiene uno o piu' articoli? Il problema puo' essere risolto banalmente con una Collection, ad esempio una lista, contenuta in Scaffale e che a sua volta contiene degli elementi Articolo:

public class Scaffale{
...
List<Articolo> articoli = ...
...
}

Nessun problema fino a quando la relazione scaffale-articolo non deve essere arricchità di nuove informazioni. Consideriamo ad esempio che si debba anche memorizzare la quantità di ogni articolo memorizzato in uno scaffale. Diverse soluzioni sono possibili al problema, e nel seguito le tratto una ad una con pregi e diffetti.

Implementazione delle informazioni aggiuntive nell'oggetto contenuto (Articolo)
Questa soluzione richiede, ad esempio, che la classe Articolo contenga al suo interno un campo quantita' che esprima il numero di pezzi di un articolo una volta che questo viene riposto in uno scaffale.
Ovviamente questa soluzione è difettosa dal punto di vista OOP: si stanno inserendo informazioni relative ad una associazione nell'elemento associato, e quest'ultimo sarà dotato di tali informazioni anche quando non associato a nessun scaffale (es. in anagrafica articoli). Inoltre, all'aumentare del numero di informazioni relative all'associazione, si ha un aumento progressivo della complessità della classe contenuta (Articolo). Questo aumento di complessità non solo rende il codice piu' complesso da manutenere, ma vincola la libreria che si sta realizzando: infatti la classe contenuta dovrà essere dotata di opportuni metodi pubblici di accesso alle informazioni di associazione (es. getQuantita() ), ma un metodo pubblico vincola la libreria e rende estremamente complesso il refactoring (non si puo' ridurre quello che è pubblico poiché non si sa a priori chi usa/userà tali metodi).

Ne consegue che questa soluzione è pessima e dovrebbe essere usata solo per esempi e programmi di estrema semplicità.


Creare una sottoclasse della classe contenuta (Articolo) e inserire nella sottoclasse le informazioni aggiuntive
Questa soluzione è una variante di quella precedente. In sostanza si crea una sottoclasse di quella contenuta (Articolo) in modo da estendere tale classe con le informazioni aggiuntive relative all'associazione. Tale sottoclasse verrà poi usata e contenuta all'interno del contenitore stesso (Scaffale), implementando di fatto una associazione che comprende sia le informazioni di base (Articolo) che quelle di associazione. Di fatto quindi si crea la sottoclasse e si specializza la classe contenitrice affinché contenga la nuova sottoclasse:

public class ArticoloEsteso extends Articolo{
private int quantita = 0;
....
}

public class Scaffale{
...
List<ArticoloEsteso> articoli;
...
}

Lo svantaggio di questa soluzione è quella di vincolare comunque le informazioni di associazione ad uno specifico articolo, ovvero ad uno specifico oggetto contenuto. Di fatto quindi è come se l'associazione si fosse spostata di livello, anziché dal contenitore ad un oggetto contenuto, da un contenitore ad un oggetto contenuto e potenziato. Inoltre questa soluzione, usando l'ereditarietà, vincola fortemente la classe estesa (come è naturale in una gerarchia).

Questa soluzione è generalmente accettabile, anche se spesso l'introduzione di un vincolo di ereditarietà impone limitazioni troppo forti per una buona manutenzione del codice.

Usare una doppia Collection nella classe contenitore (Scaffale)
L'associazione fra contenitore e contenuto viene realizzato tramite una collection, nei casi precedenti una lista di articoli contenuta in ogni istanza di scaffale. Siccome una collection può contenere un solo tipo di oggetto (tralasciamo casi in cui si memorizzano oggetti diversi poiché rappresentano casi di cattiva programmazione), può avere senso utilizzare due collection parallele: una che memorizzi l'oggetto contenuto (Articolo) e una che memorizzi le informazioni aggiuntive (ad esempio incapsulandole in un oggetto InformazioniStoccaggio). Finché le due collection saranno mantenute coerenti (ad es. l'articolo i-esimo è associato alle informazioni i-esime) non vi saranno problemi di sorta.
Chiaramente all'aumentare del numero di informazioni da memorizzare per associazione è possibile (i) modificare l'oggetto di informazioni affinché le contenga tutte oppure (ii) aggiungere una nuova lista che contenga le nuove informazioni. Ne consegue che questa soluzione è per molti versi flessibile e adattabile. Purtroppo però la gestione di due o piu' collection parallele può procurare diversi grattacapi, che possono poi sfociare in forti mal di testa quando gli indici di correlazione non corrispondano piu'.
Questa soluzione è pertanto implementabile quando si abbia un buon controllo sui meccanismi di correlazione delle collection e quando comunque queste non aumentino esageratamente (>3) in numero. Si noti infine che è opportuno implementare la politica di correlazione in modo che non sia modificabile da estensioni future, altrimenti si rischia di ricadere in problemi di correlazione difficili da individuare.

Usare una Map nella classe contenitor (Scaffale)
Questa soluzione è molto allettante, e di fatto rappresenta una naturale evoluzione della precedente. L'idea è quella di usare una Map, che altro non è che una Collection che mette in correlazione due oggetti fra loro. E' quindi possibile inserire nella mappa gli oggetti da contenere (Articolo) come chiavi, e come valori correlati oggetti di informazione:

public class Scaffale{
...
private Map
<Articolo, InformazioniStoccaggio> articoli;
...
}

Così facendo, è possibile dato un articolo ottenere in modo piuttosto semplice le informazioni correlate, come pure è possibile scorrere la mappa per avere l'elenco di ogni articolo. Di fatto, la Map realizza una doppia Collection sempre coerente.
Fino ad ora questa è la soluzione migliore, anche se presenta alcuni pericolosi trabocchetti:
  1. non deve mai essere ritornata, in nessuna eventualità, la mappa completa, bensì solo i suoi singoli elementi (Articolo e InformazioniStoccaggio). La penalità è di trovarsi nella impossibilità di fare un efficiente refactoring o di cambaire il comportamento di gestione della classe contenitrice;
  2. l'oggetto di informazioni correlate (InformazioniStoccaggio) non ha nessuna relazione autonoma con l'oggetto al quale si riferisce (Articolo), e di conseguenza dato un Articolo e un InformazioniStoccaggio non è possibile sapere se le informazioni sono correlate o provengono da sorgenti differenti. L'unica soluzione è quella di affidarsi allo Scaffale per avere le informazioni coerenti (si noti che questo problema si aveva anche nel caso di Collection correlate).
Tuttavia quelli appena esposti sono problemi generalmente piccoli, e che non pregiudicano il coding di una applicazione anche complessa, e quindi questa soluzione si presenta come una delle migliori.

Utilizzo di un Wrapper del contenuto (Articolo) e di una singola Collection nella classe contenitore (Scaffale) Questa soluzione è a mio avviso la piu' flessibile ed espandibile. L'idea è quella di usare una singola collezione, ad esempio una lista, nella classe contenitore (Scaffale), e di inserire al suo interno non direttamente l'oggetto contenuto (Articolo), bensì un wrapper di quest'ultimo che contenga anche delle informazioni supplementari. Ad esempio:

public class ArticoloInScaffale{
private Articolo articolo;
private InformazioniStoccaggio info;
...
}

public class Scaffale{
private List<ArticoloInScaffale> articoli;
....
}

In questo esempio ArticoloInScaffale ingloba Articolo, assieme alle informazioni correlate alla disposizione dell'articolo nello scaffale. E' abbastanza semplice dotare la classe Scaffale di opportuni metodi per estrarre gli articoli o le informazioni, e non si deve lavorare con collection a doppia dimensione, bensì con collection lineari. Inoltre questa soluzione è molto vantaggiosa per il code refactoring, poiché risulta molto semplice aggiungere informazioni complementari senza dover modificare il sistema di contenimento .

Conclusioni
Il metodo a mio avviso piu' portabile e manutenibile consiste nella creazione di un wrapper che colleghi ogni oggetto contenuto (Articolo) con le proprie informazioni, e l'uso di una singola collection rende molto piu' semplice da gestire la classe contenitore (Scaffale). Tuttavia questa soluzione richiede la scrittura di codice in piu', secondo la tradizione OOP dove le astrazioni regalo potenza, ma hanno un costo di setup iniziale.
Se si deve quindi realizzare una correlazione in poco tempo si utilizzi una mappa, altrimenti si usi un wrapper. Le altre soluzione sono a mio avviso da scartare per i sopracitati problemi.

Nessun commento: