|
Per una corretta comprensione di che cosa sia un Data Access Object il miglior punto di partenza è senza dubbio la definizione riportata in "Core J2EE patterns". "Use a Data Access Object (DAO) to abstract and encapsulate all access to the data source.The DAO manages the connection with the data source to obtain and store data." (Core J2EE Patterns) La definizione è molto semplice, ed in effetti è il concetto di DAO ad essere semplice: un oggetto che astrae ed incapsula la logica di accesso ad una sorgente dati. La definizione in questione offre però una visione (giustamente) generica ed astratta che deve essere poi concretizzata ed applicata alla specifica realtà nel momento in cui si decide di utilizzare questo pattern; a prima vista infatti sembrerebbe che dalla definizione si stesse parlando di un unico oggetto avente responsabilità non meglio precisate sulla natura dei dati che tratta... il fatto che il "DAO gestisce la connessione con la sorgente dati per ottenere e inserire/aggiornare/cancellare i dati" sembra quasi far pensare ad una entità monolitica che si occupa di tutti gli aspetti legati a tutti i dati rilevanti per l'applicazione. In realtà in una applicazione, fermo restando la natura ed il tipo di sorgente dati, esistono diversi DAO, ognuno dei quali si occupa di gestire gli aspetti legati all'accesso ai dati di una o più entità del Domain Model [Martin Flower]. Quindi, prendendo in considerazione un esempio per così dire ufficiale, potremmo citare il CloudScapeCatalogDAO di PetStore, che rappresenta il DAO che gestisce i meccanismi di accesso ai dati (su uno specifico sistema di storage di tipo RDBMS in particolare CloudScape) per il recupero dello stato di una o più entità di tipo Catalogo. Dando un breve sguardo a questa classe notiamo come in realtà sia molto semplice dal punto di vista concettuale; ogni metodo provvede, a partire dai dei parametri di ingresso, ad effettuare delle ricerche e restituire i relativi risultati. Quello che si vuole ottenere è per l'appunto l'incapsulamento all'interno di questa classe di tutte le operazioni necessarie (relative all'entità trattata) ad effettuare queste ricerche sulla sorgente dati (specifica) in questione. Leggendo attentamente la definizione notiamo come l'utilizzo dei DAO abbia un altro scopo oltre quello di incapsulare: quello di astrarre... Per spiegare questo concetto introduciamo innanzitutto il nostro DAO : RDBMSBandDAO (genericamente non indirizzato su uno specifico database relazionale a differenza di quello di PetStore). public class RDBMSBandDAO { public BandVO findByName(String name) throws DAOSysFindException,NoSuchResultException { // Lista contenente i risultati della ricerca List result = new LinkedList(); Connection connection = null; PreparedStatement statement = null; ResultSet rs = null; try { connection = ...ottenimento della connessione allo specifico db. statement = connection.prepareStatement("SELECT ...."); rs = statement.executeQuery(); if(rs.next()) { // Valorizzazione dei Value Objects BandVO band = new BandVO(); band.setName(rs.getString("NAME")); // ...setting delle altre properties result.add(band); // ...aggiunta VO alla lista dei risultati return result; } else { throw new NoSuchResultException(); } } catch(SQLException exception) { throw new DAOSysFindException(exception); } catch(Exception exception) { throw new DAOSysFindException(exception); } finally { // Chiusura resultset try{ if (rs != null) rs.close(); } catch (Exception ex){ } // Chiusura statement try{ if (statement != null) statement.close(); } catch (Exception ex){ } // Chiusura connessione try{ if (connection != null) connection.close(); } catch (Exception ex){ } } } public List findWhereFoundationDateIsBetween(Date start, Date end) throws DAOSysFindException { //...codice necessario ad effettuare la ricerca //...e valorizzare la lista dei value object di tipo BandVO secondo questo criterio di ricerca. } } Quello che si evince è che lo scopo del DAO è quello di incapsulare la logica di accesso alla sorgente dati per recuperare ed utilizzare lo stato (su un database di tipo relazionale) di una lista di specifiche entità (le Band ) attraverso i due metodi di ricerca: - per nome (che torna un singolo oggetto BandVO);
- per data compresa fra due estremi (che torna una lista di oggetti BandVO come risultato).
L'utilizzo più immediato è quello di istanziare la classe, e successivamente invocare il metodo di ricerca desiderato, ottenendo una lista di band che corrispondono al risultato voluto. Supponiamo che, nel bel mezzo dello sviluppo, si decida (per una ragione ignota) di cambiare database, passando ad un OODBMS (database ad oggetti); tale cambiamento però, potrebbe riverlarsi effimero, nel senso che una volta viste e valutate le differenze prestazionali rispetto al RDBMS, si potrebbe decidere di rispristinare il vecchio sistema (quello relazionale). Attualmente il codice è pieno di chiamate a RDBMSBandDAO, utilizzato in ogni dove della nostra applicazione, quindi il passaggio ad un ipotetico OODBMSBandDAO non è assolutamente indolore... Innanzitutto scriviamo il nostro OODBMSBandDAO, che possiede una interfaccia identica a RDBMSBandDAO, precisando che il recupero dei risultati per database di questo tipo avviene in maniera specifica a seconda del prodotto. Lo pseudo-codice riportato utilizza Hybernate: public class OODBMSBandDAO { public List findWhereFoundationDateIsBetween(Date a,Date b) throws DAOSysFindException { Session session = sessionFactory.openSession(); Transaction transaction = null; List result = new LinkedList(); try { transaction = session.beginTransaction(); List result = s.createQuery("select new BandVO...").list(); return result; } catch(Exception exception) { if (transaction != null) transaction.rollback(); throw new DAOSysFindException(exception); } finally { if (session != null) session.close(); } } public List findByName(String name) throws DAOSysFindException,NoSuchResultException { // Ottenimento risultati secondo questo criterio di ricerca } } Il requisito di astrazione dei Data Access Object serve proprio per fronteggiare situazioni di questo tipo. Una volta che si hanno a disposizione le due classi RDBMSBandDAO e OODBMSBandDAO, notiamo come (per definizione) esse debbano avere la stessa interfaccia. In effetti entrambi devono permettere le medesime operazioni con gli stessi parametri e tipi di ritorno. Formalizziamo questo concetto introducendo l'interfaccia BandDAO che i due DAO dovranno implementare. public interface BandDAO { public BandVO findByName(String name) throws DAOSysFindException,NoSuchResultException; public List findWhereFoundationDateIsBetween(Date start, Date end) throws DAOSysFindException; } public class RDBMSBandDAO implements BandDAO public class OODBMSBandDAO implements BandDAO A questo punto il codice è già potenzialmente flessibile, in quanto rende possibile l'utilizzo del polimorfismo. Il problema che ci si pone è che in numerosi punti della nostra applicazione è presente questa riga di codice: RDBMSBandDAO dao = new RDBMSBandDAO(); // Ottenimento dell'istanza del DAO List bands = dao.findWhereFoundationDateIsBetween(startDate,endDate); // Ricerca ed ottenimento dei risultati Ora, se vogliamo utilizzare l'OODBMSBandDAO potremmo sostiture la prima riga con OODBMSBandDAO dao = new OODBMSBandDAO(); ottendendo il risultato voluto, ma in verità in maniera poco elegante...pensate al fatto che la porzione di codice sopra descritta è presente in trenta punti dell'applicazione!! Inoltre che un domani si potrebbe decidere di passare di nuovo al RDBMSBandDAO!!! Come evitare questo inutile spreco di tempo?? Fermo restando che almeno una volta quelle benedette trenta righe vanno cambiate, quello che si può fare e cercare di fare in modo che sia la prima e l'ultima! A tale scopo presento di seguito due soluzioni, entrambi le quali si avvalgono di due Design Pattern di tipo creazionale. 3.1 Factory Method : il CatalogDAO di Java PetStore La soluzione (CatalogDAOFactory) consiste nel creare una classe con un Factory Method [GOF], dove incapsulare la logica di creazione del DAO specifico. In realtà l'implementazione suggerita dalla Gang of Four differisce leggermente dal Factory Method utilizzato da PetStore. Se dovessimo prendere alla lettera quanto riportato in "Design Patterns" la struttura del CatalogDAOFactory dovrebbe presentarsi così:  Fig. 2 Il pattern Factory Method applicato al CatalogDAO di PetStore Come si nota dal diagramma, ad ogni prodotto concreto (MySQLCatalogDAO piuttosto che CloudScapeCatalogDAO) è associata una specifica factory (rispettivamente MySQLCatalogDAOFactory e CloudScapeCatalogDAOFactory). Sia i prodotti che le factory possiedono delle interfacce comuni (CatalogDAO per i prodotti e CatalogDAOFactory per le factory) attraverso le quali il codice client lavora distaccandosi dalle reali implementazioni. L'implementazione di PetStore differisce e semplifica il diagramma riportato in Fig. 2, utilizzando il contesto java:comp/env associato agli EJB per recuperare sotto forma di stringa il nome della classe DAO da caricare e successivamente ottenendo una istanza di tale classe attraverso Class.forName(String clazz).newInstance(). In questo modo si evita di scrivere una classe factory per ogni DAO da creare, centralizzando il compito creazionale all'interno della sola CatalogDAOFactory che a questo punto diviene una classe concreta e non più un'interfaccia come nel caso precedente.  Fig. 3 L'implementazione del Factory Method utilizzato in PetStore per il CatalogDAO Due annotazioni: innanzitutto notare come l'utilizzo di una interfaccia comune ai DAO del catalogo (CatalogDAO per l'appunto) renda flessibile (e possibile) l'utilizzo di un pattern di questo tipo. Inoltre attraverso l'utilizzo del metodo Class.forName e la property nell'environment EJB che contiene il nome della classe da caricare tale metodo è completamente immune da qualsiasi cambiamento nei confronti del sistema di persistenza (e di conseguenza della implementazione dell' XXXCatalogDAO utilizzato). Da tenere d'occhio la frequenza con cui il metodo creazionale viene invocato...questo perché tutta la flessibilità offerta da un meccanismo di questo tipo ha un costo principalmente risiedente nella istanziazione dell'oggetto attraverso Class.forName().newInstance(). Provate a vedere con un piccolo main di test la differenza in termini prestazionali tra l'utilizzo di : Pippo obj = (Pippo)Class.forName("com.gazzax.sample.Pippo").newInstance() e la riga di codice: Pippo obj = new Pippo(). 3.2 Abstract Factory : ovvero una factory di factory... La seconda soluzione prevede l'introduzione del concetto di famiglie o gruppi di oggetti aventi una destinazione, uno scopo comune ed appartenti alla stessa tipologia. Nel nostro esempio, la tipologia di oggetti è genericamente Data Access Object, le famiglie sono quelle degli RDBMSDAO (DAO che accedono a database relazionali) e OODBMSDAO (DAO che accedono a database ad oggetti). Entrambi le famiglie possiedono oggetti che implementano interfacce comuni (RDBMSBandDAO e OODBMSBandDAO implementano BandDAO, RDBMSMusicianDAO e OODBMSMusicianDAO implementano MusicianDAO, etc...); Per una definizione immediata dell'Abstract Factory possiamo dire che si tratta di una factory di factory...! Vediamo di capire meglio...abbiamo detto che abbiamo (attualmente) due famiglie di oggetti DAO che accedono a database relazionali: RDBMSBandDAO à implementa BandDAO RDBMSMusicianDAO à implementa MusicianDAO DAO che accedono a database ad oggetti: OODBMSBandDAO à implementa BandDAO OODBMSMusicianDAO à implementa MusicianDAO Per ognuno di queste famiglie andiamo a creare una classe Factory // Factory per i DAO che accedono a database relazionali public class RDBMSDAOFactory { public BandDAO getBandDAO(){ return new RDBMSBandDAO(); } public MusicianDAO getMusicianDAO() { return new RDBMSMusicianDAO(); } // altri metodi creazionali... } // Factory per i DAO che accedono a database ad oggetti public class OODBMSDAOFactory { public BandDAO getBandDAO(){ return new OODBMSBandDAO(); } public MusicianDAO getMusicianDAO() { return new OODBMSMusicianDAO(); } // altri metodi creazionali... } Quello che a questo punto manca è una classe che funga da coordinatore, cioè che a partire da un codice mnemonico fornisca (crei) la factory dei DAO opportuna. Una volta ottenuta questa factory si chiama il metodo creazionale del DAO che interessa (invocandolo sulla specifica factory). Ecco perché factory di factory: prima è necessario creare la DAOFactory concreta e poi utilizzare quest'ultima per ottenere il DAO desiderato. I codici mnemonici che identificano i sistemi di storage utilizzati sono racchiuse nell'interfaccia StorageType. public interface StorageType { // Database relazionale int RDBMS = 1; // Database ad oggetti int OODBMS = 2; } Quindi andiamo a scrivere una DAOFactory... // Factory delle DAO factory public abstract class DAOFactory { // Metodo creazionale public static DAOFactory getDAOFactory(int whichFactory) { switch(whichFactory) { case StorageType.RDBMS: return new RDBMSDAOFactory(); case StorageType.OODBMS: return new OODBMSDAOFactory(); } } } ...e assicuriamoci del fatto che le due factory precedenti estendano quest'ultima; questo vincolo ci assicura che ogni futura XXXDAOFActory possieda i metodi creazionali di TUTTI i DAO necessari alla applicazione. Il codice da utilizzare per ottenere un DAO su RDBMS è il seguente: BandDAO bandDao = DAOFActory.getDAOFactory(StorageType.RDBMS).getBandDAO(); Notiamo come l'unico punto dove è presente un richiamo alla famiglia di dao da utilizzare è nell'utilizzo della variabile StorageType.RDBMS. Per il resto l'applicazione utilizza il dao attraverso la sua interfaccia (BandDAO); Il passaggio da RDBMS a OODBMS comporta la sostituzione della variabile StorageType.RDBMS con StorageType.OODBMS tutte le volte che è stato utilizzata la linea di codice appena presentata; questo può risultare scomodo da un punto di vista di manutenibilità...possiamo risolvere questo problema introducendo il concetto di DAO factory utilizzata correntemente (di default) ed aggiungere un altro metodo nella classe DAOFactory: // Metodo creazionale della XXXDAOFactory utilizzata correntemente public static DAOFactory getDefaultDAOFactory(){ return DAOFactory.getDAOFactory(StorageType.RDBMS); } L'ottenimento del dao necessario (per esempio BandDAO) si effettua attraverso: BandDAO bandDao = DAOFActory.getDefaultDAOFactory(); List result = bandDao.findByFoundationDate(startDate, endDate); o in maniera più stringata (e meno leggibile) : List result = DAOFActory.getDefaultDAOFactory().findByFoundationDate(startDate, endDate); Notare come sia scomparso ogni riferimento alla tipologia di dao richiesta (RDBMS); se un domani questa dovesse cambiare occorre aggiornare solamente il metodo getDefaultDAOFactory() facendo ritornare la factory opportuna. In questo modo il codice client lavora solamente con le interfacce (sia dei DAO che delle DAOFactory) realizzando un completo disaccoppiamento con le reali implementazioni realmente utilizzate. In Fig. 4 è presente il class diagram della struttura appena descritta.  Fig. 4 Abstract Factory applicato ai Data Access Object
|