FUNZIONE QUALITÀ E SICUREZZA Controllo delle copie Il presente documento, se non preceduto dalla pagina di controllo identificata con il numero della copia, il destinatario, la data e la firma autografa del Responsabile della Qualità, è da ritenersi copia informativa non controllata. Guida Metodologica Pattern Architetturali Java SQC609006 ver. 2 Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 1/42 FUNZIONE QUALITÀ E SICUREZZA Sommario 1 Scopo e campo di applicazione 3 2 Riferimenti 2.1 Controllo del Documento: Stato delle revisioni 2.2 Documenti esterni 2.3 Documenti interni 2.4 Termini e Definizioni 3 3 3 3 3 3 Introduzione 4 4 Design pattern 5 5 Vantaggi dei pattern 5 6 Pattern di creazione 6.1 Singleton 6.2 Factory Method 6.3 Prototype 6.4 Abstract Factory 6.5 Builder 6 6 6 8 9 10 7 Pattern di struttura 7.1 Facade 7.2 Composite/Container 7.3 Adapter 7.4 Proxy/Stub 7.5 Decorator o Filter 7.6 Flyweight 10 10 12 13 14 15 16 8 Pattern di comportamento 8.1 Template Method 8.2 Chain of Responsibility 8.3 Iterator o Enumeration 8.4 Command 8.5 Mediator 8.6 Observer 8.7 State 8.8 Strategy 8.9 Visitor 17 17 18 18 19 20 22 23 24 25 9 Presentation tier pattern 9.1 Model-View-Controller 9.2 Estensioni del pattern MVC 9.2.1 Front Controller 9.2.2 Service to Worker 9.3 View Avanzate 9.3.1 View Helper 9.3.2 Composite View 26 26 27 27 28 29 29 30 10 Business tier pattern 10.1 Composite Entity 10.2 Business Delegate 10.3 Session Facade 10.4 Service Locator 10.5 Service Activator 32 32 33 34 36 38 11 Communication Pattern 11.1 Data Transfer Object Pattern (DTO) 39 39 12 Data Access Pattern 12.1 Data Access Object Pattern (DAO) 40 41 Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 2/42 FUNZIONE QUALITÀ E SICUREZZA 1 Scopo e campo di applicazione Scopo del presente documento è fornire una panoramica dei pattern più comuni usati nella programmazione ad oggetti. Sebbene nel trattare i pattern ci si possa astrarre dal linguaggio di programmazione utilizzato, questo documento farà riferimento esplicito al loro impiego nel linguaggio Java e, più in particolare, nella tecnologia j2ee. Come conseguenza, essendo il tema trattato di natura prettamente tecnica, ovvero corredato nella sua esposizione da diagrammi, codici d’esempio, scenari comuni e casi reali, si ritiene necessario che il lettore possieda una buona conoscenza dei principi dell'object-orientation e della programmazione in Java. Un pò di pratica di programmazione è di sicuro aiuto perché affrontare i pattern avendo già una esperienza implementativa permette di riconoscerli più facilmente. 2 Riferimenti 2.1 Controllo del Documento: Stato delle revisioni Vers. 2 Descrizione delle modifiche apportate nella revisione alla versione precedente Revisione Logo Aziendale Cap. modificati N.A. 2.2 Documenti esterni • • • • • • • • • • • NORMA UNI EN ISO 9001:2008 - Sistemi di gestione per la qualità – Requisiti; NORMA UNI EN ISO 9004:2000 - Sistemi di gestione per la qualità - Linee guida per il miglioramento delle prestazioni; NORMA UNI EN ISO 9000:2005 - Sistemi di gestione per la qualità – Fondamenti e terminologia A Pattern Language: Towns, Buildings, Construction [C. Alexander; Oxford University Press; 1977] Design Patterns, Elements of Reusable Object-Oriented Software [E. Gamma, R. Helm, R. Johnson, J. Vlissides (Gang Of Four - GOF); Addison Wesley; 1995] A System of Patterns [F. Bushmann, R. Meunier, H. Rohnert, P. Sommerlad, M. Stal (Gang of Five); John Wiley & Sons; 1996] Nota Informativa - Design Patterns e Idiomi Java [E. Grasso, 1998] The Design Patterns Java Companion [J. W. Cooper; Addison Wesley; 1998] Advanced JavaServer Pages [D.M.Geary; Prentice Hall PTR; 2001] EJB™ Design Patterns - Advanced Patterns, Processes, and Idioms [F. Marinescu; John Wiley & Sons; 2002] J2EE Design Patterns [W. Crawford, J. Kaplan; O'Reilly; 2003] 2.3 Documenti interni Manuale della Qualità ACI Informatica; Sistema di Gestione per la Qualità vigente. Guida Metodologica Linee Guida Naming & Coding Conventions per Java Guida Metodologica Java Best Practices 2.4 Termini e Definizioni Per i termini, sigle e acronimi contenuti in questo documento si fa riferimento al Glossario dei termini utilizzati nel Sistema Qualità. Framework Idioma software che provvede ad uno strato di isolamento tra le applicazioni e le specifiche implementazioni di risorse o servizi al di sotto di esse. Un idioma è un soluzione di basso livello che esprime un dettaglio implementativo di un componente in relazione ad un determinato linguaggio ed alle sue caratteristiche. Un esempio può essere preso dalla programmazione Java dove uno degli idiomi più usati è il ciclo: Esempio 1 for (i=0; i<max; i++) { … //do something } Libreria Traduzione del temine inglese library. Il termine identifica in generale un insieme di funzioni, procedure o servizi a disposizione dell'utente, solitamente nelle vesti di programmatore. A grandi linee, le library possono essere classificate a seconda della tipologia di binding (ovvero la modalità con cui vengono "attaccate" al Programma che le utilizza) in dinamiche o statiche. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 3/42 FUNZIONE QUALITÀ E SICUREZZA 3 Introduzione La fase di progettazione di un sistema software è un’attività cruciale che si interpone tra la fase di analisi e quella di implementazione. Durante la fase di progettazione viene approfondito e specializzato quanto prodotto dalla precedente fase di analisi per avvicinarsi a quanto utile e specifico alla successiva fase d implementazione. E’ per questo che non è raro che la fase di progettazione introduca, rispetto ai modelli iniziali dell’analisi, nuovi oggetti, nuove responsabilità, nuove relazioni e nuove regole di collaborazione. Non che si tratti, ovviamente, di uno stravolgimento strutturale o di una decisa correzione di rotta. Fattori riconducibili a decisioni di quel tipo, del resto, dovrebbero piuttosto comportare una ripartenza o una iterazione dell’intero processo dalle sue fasi iniziali. Piuttosto si tratta dell’evoluzione del sistema dalla sua idea in astratto alla sua applicazione nel concreto, ovvero della necessità di calare la struttura ed il funzionamento teorico del sistema verso la realtà pratica dei linguaggi, delle tecnologie, degli ambienti operativi che saranno il target dell’implementazione Fare una buona progettazione ha, per questo, molto a che vedere con l’esperienza e la conoscenza pratica. In effetti, molto del lavoro di un buon progettista è fondato sulla conoscenza pratica acquisita, cioè sull’uso di soluzioni sperimentate con successo nel passato per risolvere problemi simili. In altre parole, un progettista esperto, avendo maturato la conoscenza di molteplici scenari e soluzioni ed avendo da questi fatti suoi gli elementi di invarianza distintivi di alcune classi di problemi, è in grado di riconoscere, nello specifico di ogni sistema, la possibilità e lo spazio per l’applicazione di modelli (o pattern) di soluzioni noti. La parola “pattern”, in effetti, ha un significato molto più generale dell’italiano ‘modello’ o ‘campione’; nel contesto in esame, comunque, esso indica in qualche maniera una situazione o una problematica nota per cui si ha a disposizione almeno un approccio risolutivo. Il concetto di pattern è un concetto ingegneristico ma non necessariamente informatico. Tanto è vero che la prima definizione di patterrn, risalente al 1977, si deve a Christofer Alexander che la applicò nel campo dell’ingegneria civile. Nonostante ciò la definizione data da Alexander è ancora valida ed è applicabile senza nessuna variazione all’ingegneria informatica. Infatti, secondo Alexander: Definizione 1 Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to this problem in such a way that you can use this solution a million times over, without ever doing it the same way twice. Quindi un pattern serve a risolvere un problema ricorrente documentando una soluzione provata con successo diverse volte. I pattern non hanno la pretesa di inventare nulla. Sono solo una descrizione di pratiche di buon senso che normalmente si imparano dopo lunghi tirocini di esperienza. Un pattern non è inventato, ma riconosciuto, estrapolato e classificato. Il processo è induttivo: se mi accorgo di usare una certa soluzione per risolvere problemi simili, questa soluzione può essere analizzata, estrapolata dal contesto per renderla astratta e più facilmente applicabile in altri domini, e infine catalogata, indicando nome, problema, soluzione e conseguenze. In questo processo di astrazione, la descrizione del pattern diventa sufficientemente generica perché rappresenti la soluzione a una famiglia di problemi. Un pattern funziona come uno stampo in cui le sue componenti sono istanziate nei vari casi concreti. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 4/42 FUNZIONE QUALITÀ E SICUREZZA 4 Design pattern Tra i primi ad applicare il concetto di pattern al design a oggetti individuandone i più importanti sono stati Gamma, Helm, Johnson, Vlissides (detti GOF = Gang Of Four) con il libro “Design Patterns, Elements of Reusable Object-Oriented Software”, considerato la “bibbia” dei pattern. Il libro di Gamma e soci è soprattutto un catalogo di 23 pattern, ciascuno descritto sottolineando quattro elementi essenziali: 1. Nome. Sembra ovvio, ma dare nomi indicativi ai pattern è molto importante. Un nome è un modo immediato e veloce per individuare un problema di design. Avere questi nomi nel proprio vocabolario permette di comunicare con altri designer passando complesse soluzioni di design con il semplice scambio di una parola. Dire “qui ho usato il Singleton perché dovevo essere sicuro che la risorsa fosse allocata una sola volta” comunica immediatamente la soluzione al problema e il problema stesso. 2. Problema. Per definizione un pattern serve per risolvere un problema. 3. Soluzione. Descrive la struttura del pattern: gli elementi partecipanti e le forme di collaborazione e interazione tra questi. La descrizione è sempre sufficientemente astratta da essere applicabile in diversi casi e situazioni reali. 4. Conseguenze. Ogni pattern, e quindi ogni modo per risolvere un problema, ha vantaggi e svantaggi. Proprio perché esistono infinite soluzioni ad un problema, secondo le criticità certe volte un pattern è più adatto di un altro. Identificare esplicitamente le conseguenze di un pattern aiuta a determinare l’efficacia dello stesso nei vari casi. Pur rimanendo sufficientemente generali e indipendenti dal linguaggio, i pattern della GOF sono descritti con esempi C++ e Smalltalk. Questo documento costituisce un’interpretazione dei principali pattern della GOF in ambito Java, aggiungendone altri di alto livello (ossia composti) di interesse in ambiente J2EE. 5 Vantaggi dei pattern I vantaggi derivanti dall’uso dei pattern sono numerosi, anche se non sempre evidenti. Tra i tanti se ne segnalano quattro particolarmente interessanti da un punto di ingegneristico e di ottimizzazione delle risorse: 1. Introducono un vocabolario comune. La conoscenza dei designer esperti non è organizzata secondo le regole sintattiche di un linguaggio di programmazione, ma in strutture concettuali più astratte. I pattern di design offrono un vocabolario condiviso per comunicare, documentare ed esplorare alternative di design. Permettono di descrivere un sistema in termini più astratti che non le semplici righe di codice del linguaggio di programmazione. Avere un vocabolario comune evita di dover descrivere un'intera soluzione di design: basta nominare i pattern usati. 2. Permettono di capire più facilmente il funzionamento dei sistemi ad oggetti. Molti sistemi ad oggetti complessi usano design pattern. Conoscere i pattern aumenta la comprensione dei sistemi perché risultano più chiare le scelte di design. 3. Accelerano la fase di apprendimento. Diventare esperti designer richiede molto tempo. Il modo migliore è lavorare a lungo con sistemi ad oggetti e osservare molti sistemi fatti da designer esperti. Avere conoscenza dei concetti dei pattern significa avvalersi dell’esperienza degli analisti e degli sviluppatori che ci hanno preceduto. Una raccolta di pattern è in tutto e per tutto una banca dati di conoscenze ed esperienze riutilizzabili. 4. Sono un complemento alle metodologie ad oggetti. Le metodologie ad oggetti cercano di risolvere la complessità del design standardizzando il modo con cui si affronta il problema. Ogni metodologia introduce una notazione per modellare i vari aspetti del design e un insieme di regole che indicano come e quando usare ogni elemento della notazione. L'approccio ortodosso e standardizzato delle metodologie non è mai riuscito a catturare l'esperienza dei designer. I pattern sono un complemento, il tassello mancante alle metodologie a oggetti. In particolare, il passaggio dalla fase di analisi a quella di design è sempre la più critica. Molti oggetti di design non hanno una corrispettiva entità nel dominio di analisi e i pattern sono un mezzo essenziale per spiegare questi oggetti. I tre capitoli seguenti illustrano i pattern della Gang; questi sono considerati pattern di basso livello e sono divisi in: • Pattern di creazione • Pattern di struttura • Pattern di comportamento. I rimanenti quattro capitoli illustrano pattern complessi, usati in ambito J2EE suddivisi in • Presentation tier pattern • Business tier pattern Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 5/42 FUNZIONE QUALITÀ E SICUREZZA • • Communication Pattern Data Access Pattern 6 Pattern di creazione Questi pattern aiutano a rendere un sistema indipendente da come gli oggetti sono creati, composti e rappresentati. 6.1 Singleton È il pattern più semplice e serve quando si vuole che esista una e una sola istanza di una certa classe. Il concetto chiave del Singleton è prevenire la possibilità di creare oggetti di una certa classe tramite il costruttore di new. L'idioma Java per implementare il pattern del Singleton prevede di dichiarare tutti i costruttori privati con almeno un costruttore esplicito altrimenti il compilatore genera un costruttore di default. Occorre inoltre avere: • una variabile privata statica della classe che rappresenta l'unica istanza creata; • un metodo pubblico getInstace che torna l'istanza. Questa potrebbe essere creata all'inizio o la prima volta che si richiama getInstance. Visto che Java permette la clonazione degli oggetti, per completare l'idioma la classe dovrebbe essere dichiarata final con l'effetto di impedire la clonazione delle sue istanze. Se invece la classe eredita da una gerarchia che implementa l'interfaccia Clonable occorre ridefinire il metodo clone e sollevare l'eccezione CloneNotSupportedException. Il codice seguente riporta una classe che implementa il pattern Singleton Esempio 2 final class Singleton { private static Singleton m_instance = new Singleton(); public static Singleton getInstance() { return m_istance; } private Singleton() { } public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } public void method1() { … } public void method2() { … } … } 6.2 Factory Method Questo pattern serve quando si vuole creare un oggetto di cui staticamente si conosce l'interfaccia o la super classe mentre la classe effettiva è decisa a runtime. Il pattern del Factory Method risolve il problema sfruttando il polimorfismo di Java. Una tipica struttura a framework usa classi astratte e interfacce per definire e mantenere relazioni tra oggetti. Il framework è spesso anche responsabile per la creazione di questi oggetti. Ma un framework, in quanto tale, è soggetto a essere specializzato all'interno di applicazioni che forniscono sottoclassi di interfacce e classi astratte definite nel framework. La struttura di questo pattern prevede: • AbstractObject è tipicamente un'interfaccia ma può anche essere una classe; Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 6/42 FUNZIONE QUALITÀ E SICUREZZA • • • ConcreteObject1,...,ConcreteObjectN sono classi che implementano AbstractObject; Creator è una classe con un metodo astratto createObject che torna un oggetto di tipo AbstractObject; Creator1,...,CreatorN sono sottoclassi di Creator. Esiste una classe CreatorN per ogni classe ConcreteObjectN che implementa il metodo createObject istanziando proprio un oggetto ConcreteObjectN. Se da un lato Creator e AbstractObject fanno parte del framework, CreatorN e ConcreteObjectN sono le rispettive specializzazioni per una certa applicazione. In questo modo il framework può creare oggetti di classe ConcreteObjectN senza previa conoscenza di questa classe. Il framework invoca createObject e grazie al polimorfismo l'effetto è quello di richiamare la versione specializzata del metodo nella classe CreatorN, la quale crea un'istanza di ConcreteObjectN e la restituisce con upcasting alla sua interfaccia (o super classe) AbstractObject. Questo è un aspetto importante perché il framework non conosce la classe ConcreteObjectN, ma ancora grazie al polimorfismo ogni invocazione sull'oggetto AbstractObject in realtà richiama l'implementazione definita da ConcreteObjectN. Esempio 3 // the framework interface AbstractObject { public void aMethod(); } abstract class Creator { abstract AbstractObject createObject(); AClass doSomething() { AClass pAClass = null; AbstractObject pAbstractObject = createObject(); pAbstractObject.aMethod(); … return pAClass; } } ****************************************************************** // the application class ConcreteObject1 implements AbstractObject { public void aMethod () { … } } class Creator1 extends Creator { AbstractObject createObject() { return (AbstractObject)new ConcreteObject1(); } ****************************************************************** // somewhere in the code Creator pCreator = new Creator1(); Pur raggiungendo l’obbiettivo prefissato (definire un'interfaccia per creare degli oggetti lasciando però alle sottoclassi il compito di decidere esattamente quale classe istanziare), il pattern del Factory Method ha lo svantaggio che per ogni classe ConcreteObjectN occorre definire una sottoclasse CreatorN corrispondente. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 7/42 FUNZIONE QUALITÀ E SICUREZZA 6.3 Prototype Il pattern Prototype serve negli stessi casi del Factory Method, ossia quando si vuole creare un oggetto di cui staticamente si conosce l'interfaccia o la super classe mentre la classe effettiva è decisa a runtime. Il pattern del Prototype risolve il problema sfruttando la meta informazione del linguaggio. Lo svantaggio del Factory Method è che occorre definire una classe CreatorN che implementa il metodo createObejct per ogni nuova classe ConcreteObjectN. Se il numero di classi ConcreteObject1,..., ConcreteObejctN è molto grande, o se le classi sono aggiunte a runtime tramite dynamic-class-loading, allora il Factory Method non è più adatto. In Java è possibile sfruttare Java Reflection (libreria java.lang.reflect) per avere accesso alle informazioni di meta livello che riguardano la classe di un oggetto e decidere la sua istanziazione. I partecipanti al pattern del Prototype sono: • AbstractObject è tipicamente un'interfaccia ma può essere una classe qualunque; • ConcreteObject1,...,ConcreteObjectN sono classi che implementano AbstractObject; • Creator è una classe con un metodo createObject che torna un oggetto di tipo AbstractObject; La differenza ora è che non occorre una classe CreatorN per ogni classe ConcreteObjectN. Il metodo createObejct di Creator sfrutta la meta informazione per istanziare l'oggetto. L'idioma del Prototype in Java prevede di passare come parametro il meta oggetto che rappresenta la classe che si vuole istanziare. Il codice seguente mostra un esempio di pattern prototype: Esempio 4 class Creator { AbstractObject createObject(Class pClass) { Class[] args = {...} // type of the arguments Constructor pConstructor = pClass.getConstructor(args); return (AbstractObject) pConstructor.newInstance(); } } Una variante consiste nel generalizzare ulteriormente il metodo createObject passando non il meta oggetto che rappresenta la classe bensì il nome della classe. In questo modo la classe può essere caricata dinamicamente rendendo ancora più flessibile il programma perché il nome della classe, in formato stringa, può essere ricavato nei modi più disparati (passato come parametro, comunicato da remoto o altro). Esempio 5 class Creator { AbstractObject createObject(String className) { Class pClass = Class.forName(className); return (AbstractObject) pClass.newInstance(); } } Una variante ulteriore fa ricorso alla clonazione degli oggetti invece della meta informazione. Nel testo di Gamma, infatti, il pattern del Prototype è descritto in questa variante perché in tal modo è applicabile in linguaggi come il C++ privi di meta livello. In questa variante il Creator offre un metodo aggiuntivo per registrare dinamicamente dei prototipi (da qui il nome del pattern), ossia dei rappresentanti degli oggetti da creare. I prototipi sono inseriti in un dizionario e riferiti tramite una chiave, ad esempio un nome. Il metodo createObject riceve come parametro la chiave dell'oggetto che si vuole istanziare, recupera il prototipo dal dizionario e richiama il suo metodo clone. Esempio 6 class Creator { private Hashtable m_pHashProto = new Hashtable(); void registerPrototype(String key, Object pObjProto) { m_pHashProto.put(name, pObjProto); } AbstractObject createObject(String key) { pObjProto = (AbstractObject) m_pHashProto.get(key); Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 8/42 FUNZIONE QUALITÀ E SICUREZZA if (pObjProto == null) return null; return (AbstractObject)pObjProto.clone(); } } 6.4 Abstract Factory Anche questo pattern definisce un'interfaccia per creare istanze di classi dipendenti tra loro senza conoscenza della loro classe effettiva. I pattern del Factory Method e del Prototype permettono di creare un oggetto di cui staticamente si conosce l'interfaccia o la super classe mentre la classe effettiva viene decisa a runtime. Il pattern dell'Abstract Factory risolve lo stesso problema ma in questo caso esiste una dimensione aggiuntiva: invece di una sola interfaccia o super classe AbstractObject ci sono diverse classi che in qualche modo hanno una dipendenza reciproca. I partecipanti al pattern sono: • AbstractObject1,..., AbstractObjectN sono tipicamente interfacce ma possono anche essere classi e rappresentano la famiglia di classi; • ConcreteObject1,..., ConcreteObjectN sono classi che implementano rispettivamente AbstractObject1,...,AbstractObjectN; • AbstractFactory è l'interfaccia che raccoglie i metodi di creazione della famiglia di oggetti; • ConcreteFactory implementa AbstractFactory specificando il comportamento dei metodi di creazione. Il codice seguente mostra un esempio di pattern Abstract Factory: Esempio 7 interface AbstractFactory { public AbstractObject1 createObject1(); … public AbstractObjectN createObjectN(); } **************************************************************** class ConcreteFactory implements AbstractFactory { public AbstractObject1 createObject1() { // use the Factory Method or the Prototype return (AbstractObject1)new ConcreteObject1(); } … } Si noti che il pattern dell'Abstract Factory è in realtà una generalizzazione dei pattern Factory Method e Prototype nel caso in cui le classi degli oggetti da creare sono N. È possibile quindi affermare che: Definizione 2 L'Abstract Factory è un pattern composto che usa al proprio interno il pattern Factory Method e il pattern Prototype. L'Abstract Factory isola le classi concrete e promuove la consistenza tra i ConcreteObject; al contrario dei pattern precedenti può essere utilizzato quando esistono più interfacce AbstractObject. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 9/42 FUNZIONE QUALITÀ E SICUREZZA 6.5 Builder Il pattern Builder serve per separare l'algoritmo di costruzione di una struttura di oggetti dalla creazione dei singoli oggetti. Una struttura di oggetti è formata da vari oggetti legati tra loro tramite riferimenti. Quando si crea la struttura occorre assicurarsi che tutte le sue parti siano create. Invece di distribuire la responsabilità della creazione tra i vari oggetti (un oggetto crea un altro oggetto che crea altri oggetti ecc.) è più conveniente localizzare in un unico punto la logica di creazione dell'intera struttura al fine di rendere più flessibili eventuali modifiche della struttura e del processo di creazione. I partecipanti al pattern Builder sono: • Director: incapsula l'algoritmo e la logica di creazione della struttura di oggetti; • Builder: costruisce la struttura istanziando i vari oggetti sotto la guida del Director; • Composite: è l'oggetto composto ritornato dal processo di creazione (la struttura). Il codice seguente mostra un esempio di pattern Builder: Esempio 8 class Composite { … } class Builder { createPartA(…); createPartB(…); createPartC(…); Composite getComposite(); } class Director { Builder builder; Director(Builder builder) { this.builder = builder; } Composite createComposite() { // here is the logic to build the Composite if (…) builder.createPartA(…); while (…) builder.createPartB(…); … return builder.getComposite(); } } 7 Pattern di struttura Questi pattern si occupano di come oggetti e classi sono composti per formare strutture complesse. 7.1 Facade Serve a fornire un'unica interfaccia di accesso a un sistema complesso evitando le dipendenze sulla struttura interna del sistema. Una tecnica comune per definire un design complesso è quella dei Layers, in cui un sistema complesso è stratificato in diversi livelli corrispondenti a diversi livelli di astrazione. Si parte dal livello zero che lavora con gli elementi di livello più basso, fino al livello N che fornisce le funzionalità di più alto livello. Si pensi ad esempio ad una tipica applicazione per l’accesso ai dati contenuti in un database. Il livello zero è il livello di accesso ai dati. Il livello uno usa il data access layer per fornire funzionalità più astratte. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 10/42 FUNZIONE QUALITÀ E SICUREZZA Infine, il livello finale è quello offerto direttamente all'utente che accede e vede i dati attraverso una interfaccia di tipo grafica. Il passaggio da un livello al successivo deve essere disciplinato in modo da ridurre le dipendenze tra i livelli e permettere un'evoluzione indipendente. Per ottenere ciò ogni livello definisce un'interfaccia, detta Facade, nei confronti del livello superiore che maschera la struttura degli oggetti del livello e fornisce un unico punto di accesso. È come se un intero sistema di oggetti venisse incapsulato in un unico oggetto che quindi fornisce all’esterno solo l'interfaccia del Facade. Il codice seguente mostra un esempio di pattern Facade: Esempio 9 // Layer 1 class Object1Layer1 { public void method1() { … } } class Object2Layer1 { public void method2() { … } } … class ObjectNLayer1 { public void methodN() { … } } class FacadeLayer1 { private Object1Layer1 pObject1Layer1; private Object2Layer1 pObject2Layer1; … private ObjectNLayer1 pObjectNLayer1; public void method1() { pObject1Layer1.method1(); } public void method2() { pObject2Layer1.method2(); } … public void methodN() { pObjectNLayer1.methodN(); } } **************************************************************** // Layer 2 class ObjectNLayer2 { facadeLayer1.method1(); } Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 11/42 FUNZIONE QUALITÀ E SICUREZZA 7.2 Composite/Container Il pattern Composite serve a rappresentare un oggetto complesso in una struttura con relazione di contenimento. Il tipico uso di questo pattern è all'interno di framework grafici come AWT e Swing. Gli elementi grafici che formano un'interfaccia sono organizzati secondo una gerarchia di contenimento: una finestra contiene dei pannelli che contengono dei bottoni, ecc. Nei casi in cui la relazione gerarchica di contenimento sia pervasiva nel framework è opportuno astrarre le proprietà comuni della relazione contenitore/componente all'interno di super classi specializzate. I partecipanti al pattern del Composite sono: • Component: è una classe astratta che implementa il comportamento di default degli oggetti nella gerarchia; • Container: è una classe astratta che estende Component aggiungendo il comportamento necessario per gestire le sotto componenti; • ConcreteComponent1,..., ConcreteComponentN: estendono Component e sono le classi che realizzano le componenti; • ConcreteContainer1,..., ConcreteContainerN: estendono Container e sono le classi che realizzano i contenitori; Esempi di ConcreteComponent sono Button, CheckBox, ecc. Esempi di ConcreteContainer sono Panel, ScrollPane, ecc. Quando un Component riceve un'invocazione di metodo esegue l'operazione richiesta e, nel caso il Component sia in realtà un Container, la inoltra alle sue componenti e così via in modo ricorsivo. Esempio 10 abstract class Component { // common state for all components Container parent; … // common methods for all components … } abstract class Container extends Component { // common state for all containers Component components[]; … // common methods for all containers add(Component c) { … } remove(Component c) { … } … } Un aspetto importante di questo pattern è cercare di inserire all'interno di Component e Container il maggior numero di funzionalità. In questo modo le sottoclassi ConcreteComponentN e ConcreteContainerN devono aggiungere o modificare solo una parte specializzata e ridotta di comportamento. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 12/42 FUNZIONE QUALITÀ E SICUREZZA 7.3 Adapter Il pattern Adapter permette ad una classe di supportare un'interfaccia anche quando la classe non implementa direttamente quella interfaccia. Si ricorda che una classe Java supporta un'interfaccia quando implementa l'interfaccia, ossia implementa i metodi dichiarati dall'interfaccia come nell’esempio successivo. Esempio 11 interface AnInterface { public void aMethod(); } class AClass implements AnInterface { public void aMethod() { … } } Ovviamente per fare ciò la classe deve essere costruita avendo in mente l'interfaccia, o le interfacce, che deve implementare. Gli scenari in cui si presenta la necessità di usare il pattern Adapter, sono numerosi nei casi reali e non sempre sono riconducibili ad errori di design. L’esperienza dimostra infatti che il pattern risulta particolarmente utile quando si riusa codice di terze parti o allorché è necessario aggiornare le funzionalità di un software senza voler stravolgere l’intera gerarchia delle classi. Per risolvere il problema si definisce una classe intermedia, detta Adapter, che serve ad accoppiare l'interfaccia con la classe facendo da tramite tra le due. I partecipanti al pattern dell'Adapter sono: • Target è l'interfaccia da implementare; • Adaptee è la classe che fornisce l'implementazione; • Adapter è la classe intermedia che implementa Target e richiama Adaptee. Esistono due varianti dell'Adapter: una sfrutta l'ereditarietà e l'altra la delegazione. Nel primo caso Adapter estende Adaptee, mentre nel secondo contiene il riferimento ad un oggetto di tipo Adaptee e ne delega il comportamento: Esempio 12 interface Target { public void aMethod(); } class Adaptee { public void aDifferentMethod() { … } } **************************************************************** //First solution class InheritanceAdapter extends Adaptee implements Target { public void aMethod() { aDifferentMethod(); } } **************************************************************** //Second solution class DelegationAdapter implements Target { private Adaptee adaptee; DelegationAdapter(Adaptee a) { adaptee = a; } public void aMethod() { Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 13/42 FUNZIONE QUALITÀ E SICUREZZA adaptee.aDifferentMethod(); } } L'approccio basato su ereditarietà ha le seguenti caratteristiche: • dato che estende Adaptee, non può funzionare per oggetti che sono istanze di sottoclassi di Adaptee; • permette di modificare parte del comportamento dei metodi di Adaptee, con vantaggi e svantaggi conseguenti; • non introduce nuovi oggetti e riferimenti, per cui il design è più pulito e facile da capire. L'approccio basato su delegazione ha queste caratteristiche: • permette a un'unica classe Adapter di lavorare con Adaptee e tutte le sue sottoclassi; • introduce un oggetto ulteriore complicando il design; 7.4 Proxy/Stub Serve quando si vuole introdurre un surrogato, o rappresentante, di un oggetto. Uno dei motivi per introdurre un surrogato è quello di mantenere lo stesso livello di astrazione che si avrebbe nell'accedere direttamente all'oggetto mascherando complessità aggiuntive. Ad esempio, in Java RMI il client di un oggetto remoto non deve preoccuparsi delle problematiche inerenti alla comunicazione (protocolli, marshaling dei parametri, ecc.). Il client accede all'oggetto remoto come se fosse un oggetto locale. Per fare questo Java RMI introduce lato client un oggetto Proxy, detto Stub nella terminologia RMI, che fornisce la stessa interfaccia del server e incapsula la complessità della comunicazione remota. Un altro motivo è dare una rappresentazione object-oriented ad elementi che non sono oggetti. Ad esempio, un sistema ad oggetti che si interfaccia a un database relazionale può incapsulare la logica di accesso al database all'interno di oggetti Proxy. In questo modo il client rimane ignaro dell'esistenza del database e dalla sua complessità di accesso. In molti dei casi reali (RMI, EJB, Web Services) il pattern è applicato usando le classi STUB generate da tool automatici quali ad esempio rmic. I partecipanti al pattern del Proxy sono: • Target è l'interfaccia dell'oggetto server remoto; • Server è la classe che implementa Target • Proxy fornisce la stessa interfaccia Target e incapsula la complessità per accedere a Server. Esempio 13 interface Target extends Remote { public void aMethod(); } class Server extends UnicastRemoteObject implements Target { public void aMethod() { … } } // generated by the rmi compiler (rmic) class ServerStub extends RemoteStub implements Target { public void aMethod() { // forward the invocation to the remote object … } } Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 14/42 FUNZIONE QUALITÀ E SICUREZZA 7.5 Decorator o Filter Il pattern Decorator permette di aggiungere o modificare dinamicamente il comportamento di un oggetto. Il modo tradizionale nei linguaggi ad oggetti per aggiungere o modificare il comportamento di una classe è tramite ereditarietà in cui si definisce una sottoclasse che introduce i cambiamenti richiesti. Tuttavia alle volte occorre aggiungere o modificare il comportamento di un singolo oggetto e non di una classe perché si vuole che il nuovo comportamento sia introdotto a runtime. L'effetto si ottiene appunto con il pattern del Decorator, anche se il nome Filter corrisponde alla nomenclatura Java del package java.io dove questo pattern viene ampiamente usato nella gestione degli stream e rende meglio l'idea del suo funzionamento. I partecipanti al pattern sono: • Target è la classe dell'oggetto le cui funzionalità devono essere modificate a runtime; • Decorator è un oggetto interposto davanti a Target che offre la stessa interfaccia e introduce il nuovo comportamento delegando poi a Target l'esecuzione normale. Esempio 14 interface ItfTarget { public void aMethod(); } class Target implements ItfTarget { public void aMethod() { // some behavior … } } class Decorator extends Target { private Target m_pTarget; Decorator(Target pTarget) { m_pTarget = pTarget; } public void aMethod() { // some additional behavior … target.aMethod(); // some additional behavior … } } È possibile fare due considerazioni su questo pattern: • Target può essere un'interfaccia o una classe. Nel primo caso il design è più pulito perché Decorator può implementare direttamente Target. Nel secondo caso Decorator deve estendere Target con lo svantaggio di duplicare lo stato dell'oggetto; • Il pattern del Decorator può essere ricorsivo, nel senso che è possibile mettere davanti a un Decorator un altro Decorator e così via, costruendo una catena di Decorator. L'effetto è di avare una granularità molto fine sulla modifica dinamica del comportamento. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 15/42 FUNZIONE QUALITÀ E SICUREZZA 7.6 Flyweight Questo pattern permette di condividere oggetti qualora un loro numero elevato avesse costi proibitivi di efficienza e gestione. Un design ad oggetti spinto all'estremo può portare alla definizione di gran numero di classi dove ogni entità del dominio è rappresentata come un oggetto. In questi casi il numero di oggetti creati a runtime è elevato e il sistema potrebbe collassare sotto il peso della gestione della memoria. Ecco perché spesso occorre fare compromessi sulla granularità delle classi e numero di oggetti istanziati Il pattern del Flyweight risolve questo genere di problema. Un Flyweight è un oggetto condiviso in contesti diversi e ogni contesto vede il Flyweight come un oggetto indipendente. Dal punto di vista logico è come se fossero creati oggetti diversi, mentre gli oggetti sono condivisi fisicamente. Nel paradigma object-oriented ogni oggetto, per definizione, incapsula uno stato proprio e oggetti diversi hanno uno stato diverso. Fare credere che uno stesso oggetto si comporti come due o più oggetti sembra contrario ai principi dell'object-orientation. Nel pattern del Flyweight il punto chiave è distinguere tra stato indipendente e stato dipendente dal contesto. Lo stato indipendente dal contesto è quello che non varia e può rimanere all'interno del Flyweight. Lo stato dipendente dal contesto deve essere "esternalizzato", ossia rimosso dal Flyweight e passato come parametro dei metodi. I partecipanti al pattern del Flyweight sono: • Flyweight: è l'oggetto condiviso. Incapsula lo stato indipendente dal contesto e riceve quello dipendente come parametro dei suoi metodi; • FlyweightFactory: crea e gestisce i Flyweight. I client devono ottenere i riferimenti ai Flyweight tramite la FlyweightFactory e mai istanziare direttamente i Flyweight. La FlyweightFactory mantiene un dizionario (hashtable) e permette ai client di individuare un Flyweight di interesse passando una chiave, ad esempio un nome. Esempio 15 class Flyweight { // independent state void aMethod(dependentSate pDependentSate) … } } class FlyweightFactory { private Hashtable regHashtable = new Hashtable(); Flyweight getFlyweight(Object key) { Flyweight pFlyweight = (Flyweight)getFlyweight.get(key); if (pFlyweight == null) { pFlyweight = new Flyweight(); getFlyweight.put(key, pFlyweight); } return pFlyweight; } } Un esempio di caso d’uso del pattern Flyweight è la realizzazione di un word processor. In questo scenario si potrebbe creare un oggetto per ogni carattere digitato. L’oggetto Carattere dovrebbe contenere informazioni come il font, la dimensione e il colore di ogni carattere. Il problema è che un testo lungo potrebbe contenere migliaia di caratteri, e oggetti. Il pattern Flyweight risolve il problema creando un nuovo oggetto per memorizzare quelle informazioni che sono condivise da tutti i caratteri con la stessa formattazione. Memorizzando le informazioni una volta sola si ha un grosso risparmio di memoria. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 16/42 FUNZIONE QUALITÀ E SICUREZZA 8 Pattern di comportamento Questi pattern si occupano degli algoritmi e della distribuzione di responsabilità tra oggetti: non dunque delle relazioni tra oggetti e classi, ma del loro modo di comunicare e del flusso di controllo a runtime. 8.1 Template Method Permette di definire un algoritmo in una classe lasciando alle sottoclassi la possibilità di modificare alcune parti dell'algoritmo senza alterarne la struttura. Questo è in assoluto uno dei pattern più importanti nel design ad oggetti perché costituisce il modo più pulito e controllato per il riuso del codice mediante ereditarietà. L'ereditarietà è lo strumento che permette alle sottoclassi di modificare il comportamento di una classe. La granularità della modifica è a livello di metodo: se si ridefinisce un metodo occorre riscrivere tutto il comportamento del metodo. Alle volte capita di voler definire un algoritmo all'interno di un metodo che deve rimanere inalterato nella sua struttura principale, e allo stesso tempo si vuole dare la possibilità alle sottoclassi di ridefinire alcuni passi dell'algoritmo. I partecipanti al pattern del Template Method sono: • AbstractClass implementa il Template Method che definisce la struttura dell'algoritmo e invoca altri metodi astratti come passi dell'algoritmo; • ConcreteClass estende AbstractClass e implementa i metodi astratti, ossia i passi dell'algoritmo. Il metodo templateMethod è privato, ragion per cui le sottoclassi non possono ridefinire l'algoritmo. Una variante del pattern del Template Method non dichiara i passi dell'algoritmo come metodi astratti bensì fornisce un comportamento di default a tali metodi, eventualmente nullo. È estremamente importante quando si definisce il templateMethod precisare quali passi dell'algoritmo devono essere implementati dalle sottoclassi, e in tal caso andranno dichiarati astratti e quali possono essere implementati, e in tal caso avranno un comportamento di default. Questo approccio prende il nome di Hollywood Principle con il motto “don't call us, we'll call you”, ossia il fatto che è la super classe a richiamare i metodi della sottoclasse e non viceversa come succede applicando alla regola il concetto dell'ereditarietà. Esempio 16 abstract class AbstractClass { private void templateMethod() { // here's the algoritm … method1(); while (…) method2(); … method3(); } abstract void method1(); // must be redefined void method2() { } behaviour: can be redefined abstract void method3(); // null // must be redefined } class ConcreteClass extends AbstractClass { void method1() { … } void method2() { … } void method3() { … } } Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 17/42 FUNZIONE QUALITÀ E SICUREZZA 8.2 Chain of Responsibility Questo pattern serve per avere una catena di oggetti dove ogni oggetto può decidere se eseguire l'operazione richiesta o delegarla all'oggetto successivo. Il pattern Chain of Responsibility è la generalizzazione del concetto di delegazione dei linguaggi ad oggetti. Spesso due o più oggetti hanno una relazione di delegazione dove un oggetto, il delegante, delega la responsabilità per l'esecuzione di una certa funzionalità a un secondo oggetto, il delegato. Se il delegato delega a sua volta l'esecuzione della funzionalità a un terzo oggetto otteniamo una catena di delegazione delle responsabilità. I partecipanti al pattern Chain of Responsibility sono: • Handler è l'interfaccia comune di tutti i delegati; • ConcreteHandler1,..., ConcreteHandlerN implementano Handler e mantengono il riferimento al prossimo delegato. Esempio 17 interface Handler { public void aMethod(); … } class ConcreteHandler1 implements Handler { private Handler nextHandler; … public void aMethod() { if (…) { // handle request } else { next.aMethod(); } } } 8.3 Iterator o Enumeration Questo pattern fornisce un modo per accedere agli elementi di una collezione di oggetti in modo sequenziale senza esporre la struttura interna della collezione. Una collezione di oggetti, come una lista, un vettore o un dizionario (hashtable), deve dare modo di percorrere i suoi elementi uno dopo l'altro, magari per eseguire una certa operazione su tutti. L'idea del pattern dell'Iterator è di estrarre la responsabilità per l'attraversamento della collezione all'interno di un oggetto iteratore. In tal modo si possono definire iteratori con diverse politiche di attraversamento senza doverle prevedere all'interno della collezione. I partecipanti al pattern sono: • Aggregate definisce l'interfaccia per l'accesso alla collezione; • ConcreteAggregate è la classe che implementa Aggregate e contiene la struttura della collezione; • Iterator definisce l'interfaccia dell'iteratore; • ConcreteIterator implementa Iterator e mantiene il riferimento all'elemento corrente nella collezione. Ogni collezione è responsabile della creazione dei suoi iteratori. Quindi l'interfaccia Aggregate deve definire un metodo createIterator implementato dalla classe ConcreteIterator usando il pattern del Factory Method. Java fornisce nativamente l’implementazione degli Iterator per le collezioni che implementano direttamente o indirettamente l’interfaccia java.util.List, dove si specifica il metodo listIterator per la restituzione di uno dei tipi di iterator della lista. Esistono tre interfacce di iteraror nelle API di Java: • java.util.Enumeration: è l’interfaccia dell’iterator più semplice, che fornisce soltanto i metodi hasMoreElements() (per determinare se ci sono più elementi) e nextElement() (per spostarsi all’elemento successivo). La classe java.util.StringTokenizer utilizza un oggetto che implementa questa interfaccia per l’identificazione dei diversi token di una stringa. • java.util.Iterator: replica la funzionalità dell’interfaccia Enumeration, con i metodi hasNext() e next(), e aggiunge il metodo remove() per l’eliminazione dell’elemento corrente. • java.util.ListIterator: estende l’interfaccia Iterator, aggiungendo i metodi hasPrevious() (per determinare se ci sono elementi precedenti quello corrente), previous() (per spostarsi Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 18/42 FUNZIONE QUALITÀ E SICUREZZA all’elemento precedente), nextIndex() e previousIndex() (per conoscere l’indice dell’elemento seguente e di quello prcedente), setObject(Object o) per settare l’oggetto fornito come parametro nella posizione corrente, e addObject(Object o) per aggiungere un elemento alla collezione. 8.4 Command Questo pattern serve a trattare le richieste di operazioni come oggetti eliminando la dipendenza tra l'oggetto che invoca l'operazione e l'oggetto che ha la conoscenza di come eseguirla e permettere gestioni sofisticate di accodamento, sincronizzazione, gestione delle priorità, ecc. Alle volte è necessario inoltrare una richiesta a un oggetto senza conoscere il tipo di richiesta e l'oggetto che la deve trattare. Un esempio è il caso delle invocazioni a oggetti remoti in Java tramite RMI. La componente che gestisce il protocollo di comunicazione non tratta direttamente la richiesta bensì la inoltra all'oggetto destinatario (dispatching). Un altro esempio è il caso in cui si vogliono disciplinare le invocazioni concorrenti a un oggetto in modo da gestire in modo sofisticato la coda di priorità delle richieste. Un oggetto Scheduler può decidere l'ordine di esecuzione e la politica di conflitto delle richieste. Il pattern del Command risolve il problema trasformando un'invocazione in un oggetto per permettere una gestione sofisticata di accodamento ed esecuzione delle richieste. I partecipanti al pattern del Command sono: • Command definisce un'interfaccia per eseguire un'azione execute; • ConcreteCommnad implementa Command e definisce il comportamento del metodo execute; • Invoker crea un'istanza di ConcreteCommand e la passa allo Scheduler chiamando manageCommand; • Scheduler è un'interfaccia che definisce il metodo manageCommand; • ConcreteScheduler implementa manageCommand e gestisce le istanze di ConcreteCommand secondo una certa politica. Quando un Command deve essere eseguito lo Scheduler chiama il metodo execute. L'Invoker crea una istanza di ConcreteCommand e lo passa allo Scheduler. Lo Scheduler accoda il comando e in un momento opportuno chiama il metodo execute. Esempio 18 interface Command { public void execute(); } class ConcreteCommand implements Command { private ConnectionManager manager; ConcreteCommand (ConnectionManager m) { manager = m; } public void execute() { manager.connect(); } } interface Scheduler { public void manageCommand(Command c); } // the ConcreteScheduler forks a thread pool to deal with the // requests class ConcreteScheduler implements Scheduler { ConcreteScheduler(int poolSize) { for (int i = 0; i < poolSize; ++i) new Thread(this).start(); } public synchronized void manageCommand(Command c) { queue(c); notify(); } Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 19/42 FUNZIONE QUALITÀ E SICUREZZA public void run() { while (true) { Command c; synchronized (this) { while (emptyQueue()) wait(); c = unqueue(); } c.execute(); } } } class Invoker { … Command c = new ConcreteCommand (connectionManager); Scheduler.manageCommand(c); … } 8.5 Mediator Questo pattern serve a ridurre i riferimenti espliciti tra oggetti incapsulando le loro regole di interazione in un oggetto separato. Un pattern con l'obiettivo di concentrare il comportamento invece di distribuirlo sembra andare contro i principi dell'object-orientation che invece incoraggia la distribuzione del comportamento e delle responsabilità tra gli oggetti. Sebbene partizionare un sistema in diversi oggetti aumenti la riusabilità, una complessa ragnatela di interconnessioni tra oggetti finisce per ridurla perché le dipendenze possono essere talmente complesse da rendere difficile ogni forma di evoluzione e modifica. Diventa problematico modificare il comportamento di un sistema quando la modifica di un oggetto può richiedere la modifica degli oggetti interconnessi a questo e così via. Nel pattern del Mediator esiste un oggetto mediatore che evita i riferimenti espliciti tra gli oggetti e controlla e coordina la loro interazione. Siccome ogni oggetto dialoga solo con il mediatore è possibile modificare il comportamento di un oggetto senza che questo abbia impatti sugli altri. I partecipanti al pattern del Mediator sono: • Mediator definisce un'interfaccia per permettere la comunicazione tra i vari oggetti. Gli oggetti usano questa interfaccia per mandare e ricevere richieste; • ConcreteMediator implementa Mediator e incapsula il comportamento di coordinamento tra gli oggetti. Supponiamo, per esempio, di avere tre classi interconesse. Esempio 19 class AClass { private AnOtherClass pAnOtherClass; private YetAnOtherClass pYetAnOtherClass; void doIt() { pAnOtherClass.reset(); pYetAnOtherClass.doIt(); } void print(String s) { … } } class AnOtherClass { private AClass pAClass; void reset() { … } void print(String sString) { Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 20/42 FUNZIONE QUALITÀ E SICUREZZA pAClass.print(sString); } } class YetAnOtherClass { private AClass pAClass; void doIt() { … } void print(String sString) { pAClass.print(sString); } Usando il pattern Mediator possiamo evitare i riferimenti reciprochi nel seguente modo: Esempio 20 interface Mediator { void doIt(); void print(String sString); } class ConcreteMediator implements Mediator{ private AClass pAClass; private AnOtherClass pAnOtherClass; private YetAnOtherClass pYetAnOtherClass; void doIt() { pAnOtherClass.reset(); pYetAnOtherClass.doIt(); } void print(String sString) { pAClass.print(sString); } } class AClass { private Mediator m_pMediator; void doIt() { m_pMediator.doIt(); } void print(String sString) { … } } class AnOtherClass { private Mediator m_pMediator; void reset() { … } void print(String sString) { m_pMediator.print(sString); } } class YetAnOtherClass { private Mediator m_pMediator; void doIt() { … } Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 21/42 FUNZIONE QUALITÀ E SICUREZZA void print(String sString) { m_pMediator.print(sString); } } 8.6 Observer Questo pattern definisce una relazione tra un oggetto e un insieme di oggetti interessati a essere notificati quando il primo oggetto cambia di stato. Quando si partiziona un sistema in un insieme di oggetti cooperanti è importante mantenere gli oggetti consistenti tra loro quando cambiano di stato. Spesso l'oggetto che cambia di stato deve essere indipendente dagli oggetti interessati al cambiamento permettendo che il loro numero e identità possano variare dinamicamente. Mantenere riferimenti espliciti agli oggetti interessati produce un design complesso e poco flessibile. Il pattern del Mediator, descritto sopra, propone una soluzione centralizzata al problema. Un modo per affrontare il problema in modo distribuito anziché centralizzato è proposto dal pattern dell'Observer. I partecipanti al pattern sono: • Observer è l'interfaccia che specifica il tipo di notifiche di eventi di interesse; • ConcreteObserver implementa Observer e specifica il comportamento dei metodi in risposta agli eventi; • Subject è la classe che cambia di stato e avverte gli Observers quando tali cambiamenti occorrono. Deve offrire due metodi per rispettivamente aggiungere e rimuovere gli Observer dalla lista degli interessati. Un Subject ha due modi per comunicare i cambiamenti agli Observer. Nel primo modo il Subject passa come parametro di notifica tutta l'informazione necessaria per capire cosa è cambiato. Nel secondo modo il Subject non passa informazione, o passa informazione incompleta, e gli Observer interrogano il Subject se necessitano di informazione aggiuntiva. I due approcci hanno vantaggi e svantaggi. Il primo rischia di passare troppa informazione ma evita una comunicazione incrociata che potrebbe causare problemi di sincronizzazione (metodi synchronized in Java). Il secondo approccio ha vantaggi e svantaggi opposti. Esempio 21 interface Observer { public void update(); } class ConcreteObserver implements Observer { public void update(){ … } } class Subject { private AClass m_pAClass; public void addObserver(Observer o) { … } public void removeObserver(Observer o) { … } void setValue(AClass pAClass) { … for (Enumeration e = observers.elements(); e.hasMoreElements(); ){ Observer o (Observer)e.nextElement(); o.update(); } … } Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 22/42 FUNZIONE QUALITÀ E SICUREZZA 8.7 State Questo pattern (noto anche con il nome di Automata) permette a un oggetto di cambiare comportamento quando cambia il suo stato interno. Consideriamo il caso di un oggetto che riflette il comportamento di un automa a stati. Ad esempio un oggetto che rappresenta una connessione TCP può essere in diversi stati: established, listening, closed, ecc… In corrispondenza di ogni stato della connessione il comportamento sarà diverso in risposta a richieste quali open, close, send, ecc… Un modo per implementare un oggetto come automa a stati è avere per ciascun metodo dell'oggetto un controllo esplicito su quale stato si trova l'oggetto e agire di conseguenza. Quindi i vari metodi contengono una serie di if, else if, o switch che spesso rendono complesso il codice e difficile da modificare. Se aggiungiamo un nuovo stato dell'automa, occorre modificare tutti i metodi coinvolti introducendo un nuovo ramo condizionale. Il pattern dello State offre un modo per implementare un automa a stati mantenendo più aderenza ai principi dell'object-orientation. L'idea è di definire una classe diversa per ogni stato dell'automa contenente la logica da eseguire in corrispondenza di quello stato. La classe mantiene anche i riferimenti agli stati adiacenti corrispondenti a una transizione, proprio come si modellerebbe un automa a stati. Aggiungere un nuovo stato corrisponde a definire una nuova classe con impatti limitati sulle altre. I partecipanti al pattern sono: • State è l'interfaccia che definisce le operazioni da eseguire; • ConcreteState1,..., ConcreteStateN sono le classi che implementano State e corrispondono ai vari stati dell'automa; • Context è la classe che maschera e gestisce i vari stati dell'automa. I clients agiscono su Context e non vedono gli oggetti State. Esempio 22 interface State { public void doIt(); … } // the context class Context { private State m_pState; void setState(State pState) { this.m_pState = pState; } public void doIt(){ m_pState.doIt(this); } } // the first concrete state class ConcreteState1 implements State { public void doIt(Context pContext) { … pContext.setState(new ConcreteState1()); } } // the second concrete state class ConcreteState2 implements State { public void doIt(Context pContext) { … pContext.setState(new ConcreteState2()); } } Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 23/42 FUNZIONE QUALITÀ E SICUREZZA 8.8 Strategy Questo pattern permette di incapsulare una famiglia di algoritmi sotto forma di oggetti intercambiabili. Per definizione un oggetto incapsula stato e comportamento. Il comportamento è dato dai metodi dell'oggetto che agiscono sullo stato. L'ereditarietà permette alle sottoclassi di ridefinire i metodi con algoritmi diversi. In alcuni casi potrebbe essere utile generalizzare il meccanismo per modificare il comportamento tanto da confinare comportamenti diversi in classi diverse. Il risultato è separare in classi diverse lo stato di un oggetto e il comportamento che agisce sullo stato permettendo grande flessibilità di combinazione tra i due, anche a runtime, cosa non possibile con l'ereditarietà. Non solo lo stato può essere accoppiato con algoritmi diversi, ma uno stesso algoritmo può essere applicato a diverse strutture dati. Ciò è possibile applicando il pattern dello Strategy I partecipanti al pattern dello Strategy sono: • Strategy è l'interfaccia comune alla famiglia di algoritmi; • ConcreteStrategy implementa Strategy e contiene un algoritmo specifico; • Context è la struttura dati su cui agisce un oggetto Strategy. Esempio 23 // the strategy interface Strategy { void doSomething(); void doSomethingOther(); … } // the first concrete strategy class ConcreteStrategy implements Strategy { … void doSomething () { … } void doSomethingOther () { … } } // the second concrete strategy class ConcreteStrategy2 implements Strategy { … void doSomething () { … } void doSomethingOther () { … } } class Context { … private Strategy m_pStrategy; void setStrategy(Strategy pStrategy) { this.m_pStrategy = pStrategy; } void doSomething () { m_pStrategy.doSomething(); } void doSomethingOther () { m_pStrategy.doSomethingOther(); } } Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 24/42 FUNZIONE QUALITÀ E SICUREZZA 8.9 Visitor Questo pattern permette di separare le classi di una struttura di elementi dalle operazioni applicate a questi. Capita spesso di organizzare oggetti in strutture dati complesse, come nel pattern del Composite. Consideriamo il caso in cui si vuole definire un'operazione su tutti gli elementi della struttura. Un modo è aggiungere l'operazione come metodo di tutte le classi coinvolte nella struttura. Questa soluzione è adatta se le operazioni da eseguire si possono prevedere nel momento in cui si progetta la struttura. Se per contro le operazioni applicabili sono decise in momenti successivi o possono variare con più facilità rispetto alla struttura, allora aggiungere e modificare ogni volta le classi della struttura produce un sistema difficile da capire, mantenere e modificare, senza considerare che le classi della struttura sono classi di libreria e quindi non possono essere modificate. Il pattern del Visitor permette di confinare le operazioni in classi separate rendendo le classi della struttura indipendenti da aggiunta e modifiche di operazioni. I partecipanti al pattern del Visitor sono: • Element è un'interfaccia con l'operazione accept che riceve un Visitor come argomento; • ConcreteElementA,..., ConcreteElementZ implementano Element. Il metodo accept deve essere implementato con la tecnica del double-dispatch; • Visitor è un'interfaccia che definisce tante versioni dell'operazione visit quante sono le classi ConcreteElementX; • ConcreteVisitor1,..., ConcreteVisitorN implementano Visitor. Ogni metodo visit(ConcreteElementX) implementa la versione dell'operazione da applicare alla classe ConcreteElementX. Esempio 24 interface Element { public void accept(Visitor visitor); } interface Visitor { public void visit(ConcreteElementA element); … public void visit(ConcreteElementZ element); } class ConcreteElementA implements Element { public void accept(Visitor visitor) { visitor.visit(this); } // any other state and behaviour … } class ConcreteVisitor1 implements Visitor { public void visit(ConcreteElementA element) { // perform operation 1 on element A } … } Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 25/42 FUNZIONE QUALITÀ E SICUREZZA 9 Presentation tier pattern 9.1 Model-View-Controller Permette di avere una applicazione di natura modulare e basata sulle responsabilità, al fine di ottenere una vera e propria applicazione component-based. Questo è conveniente per poter più facilmente gestire la manutenzione dell'applicazione. Con la massiccia diffusione delle applicazioni enterprise, non è possibile prevedere al momento dello sviluppo, in che modo e con quale tecnologia gli utenti interagiranno con il sistema (WML, XML, WI-FI, HTML, …). Appare quindi chiaro il bisogno di un'architettura che permette la separazione netta tra i componenti software che gestiscono il modo di presentare i dati, e i componenti che gestiscono i dati stessi. Se l'interfaccia grafica è altamente integrata con la logica di servizio dell'applicazione, ogni cambiamento all'interfaccia ha impatti sull'intera applicazione rendendo il processo di adattamento altamente costoso. Il pattern del Model-View-Controller (MVC) separa gli oggetti responsabili della visualizzazione grafica e controllo dagli oggetti che contengono i dati e la logica dell'applicazione. Grazie a questa separazione è possibile progettare interfacce grafiche in cui la stessa informazione è visualizzata in modi diversi. Ad esempio, le informazioni riguardanti la distribuzione delle targhe di una certa provincia rispetto alle fasce di età potrebbe essere visualizzata contemporaneamente in una tabella, in un diagramma a torta e uno a barre. Se l'utente cambia alcuni dati nella tabella la modifica si riflette immediatamente sugli altri due diagrammi. Questo è possibile perché le tre viste fanno riferimento alla stessa informazione contenuta in un oggetto separato. I partecipanti del pattern MVC sono: • Model: implementa il core dell'applicazione incapsulandone lo stato e definisce i dati e le operazioni che possono essere eseguite su questi. Quindi definisce le regole di business per l'interazione con i dati, esponendo alla View ed al Controller rispettivamente le funzionalità per l'accesso e l'aggiornamento. Per lo sviluppo del Model quindi è vivamente consigliato utilizzare le tipiche tecniche di progettazione object oriented al fine di ottenere un componente software che astragga al meglio concetti importati dal mondo reale. Il Model può inoltre avere la responsabilità di notificare ai componenti della View eventuali aggiornamenti verificatisi in seguito a richieste del Controller, al fine di permettere alle View di presentare agli occhi degli utenti dati sempre aggiornati. In tecnologia J2EE il Model è in genere costituito dai Java Bean o dagli EJB. • View: gestisce la logica di presentazione dei dati. Ciò implica che questa deve fondamentalmente gestire la costruzione dell'interfaccia grafica (GUI) che rappresenta il mezzo mediante il quale gli utenti interagiranno con il sistema. Ogni GUI può essere costituita da schermate diverse che presentano più modi di interagire con i dati dell'applicazione. Per far sì che i dati presentati siano sempre aggiornati è possibile adottare due strategie note come “push model" e "pull model". Il push model adotta il pattern Observer [6.6], registrando le View come osservatori del Model. Le View possono quindi richiedere gli aggiornamenti al Model in tempo reale grazie alla notifica di quest'ultimo. Benché questa rappresenti la strategia ideale, non è sempre applicabile. Ad esempio se le View, che sono implementate con JSP, restituiscono GUI costituite solo da contenuti statici (HTML) e quindi non in grado di registrarsi come osservatori sul Model. In tali casi è possibile utilizzare il "pull Model" dove la View richiede gli aggiornamenti quando "lo ritiene opportuno". Inoltre la View delega al Controller l'esecuzione dei processi richiesti dall'utente dopo averne catturato gli input e la scelta delle eventuali schermate da presentare. In tecnologia J2EE il View è in genere costituito dalle pagine JSP si occupano di gestire gli aspetti di rendering (HTML, WML, XML, ecc.) dei dati applicativi. • Controller: questo componente ha la responsabilità di trasformare le interazioni dell'utente della View in azioni eseguite dal Model. Ma il Controller non rappresenta un semplice "ponte" tra View e Model. Realizzando la mappatura tra input dell'utente e processi eseguiti dal Model e selezionando le schermate della View richieste, il Controller implementa la logica di controllo dell'applicazione. In tecnologia J2EE il controller è in genere costituito da entità modellabile come Servlet e classi dette RequestHandler. Il pattern MVC è un pattern architetturale complesso; in altri termini si tratta di un pattern che usa diversi altri pattern più semplici al suo interno. Per esempio: • L'accoppiamento lasco tra Model e View/Controler può usare il pattern dell'Observer. View e Controller si registrano come Observer nei confronti del Model permettendo di avere diverse Views per lo stesso Model; Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 26/42 FUNZIONE QUALITÀ E SICUREZZA • La separazione View/Controller è un esempio del pattern Strategy. Una View può essere associata a diverse strategie in risposta alle azioni utente, ossia a diversi Controller. Ad esempio, una View disabilitata (shadow) può essere associata ad un Controller che ignora ogni azione utente. Lo schema funzionale per il pattern MVC è il seguente: Figura 1 - Pattern MVC: Class Diagram 9.2 Estensioni del pattern MVC 9.2.1 Front Controller Il pattern del Front Controller permette di avere un punto di accesso centralizzato per la gestione delle richieste al presentation tier. Il pattern MVC non specifica quanti controller definire per una applicazione. Nel caso generale è possibile creare tanti controller quanti sono le possibili pagine di output dell’applicazione. In alternativa è possibile creare un singolo controller, onnisciente, capace di gestire tutte le possibili request e generare le response per ciascuna di essa. Nell’ottica di costruire una applicazione estendibile e manutenibile, nessuna delle due soluzioni proposte appare soddisfacente. Avere un controller per ogni pagina significa avere un gran quantità di classi e una applicazione difficile da estendere. L’alternativa potrebbe presentare problemi simili: mentre, infatti sarebbe facile aggiungere nuove funzioni comuni, l’implementazione di funzionamenti particolari per alcune View potrebbe risultare laboriosa. La soluzione a questo problema è rappresentata dal pattern del Front Controller. Il Front Controller fornisce un singolo punto che incapsula le elaborazioni comuni a tutte le request e quindi trasmette le richieste ai controller specifici di ogni pagina. Sebbene il front controller processi ogni richiesta, esso non contiene il codice necessario a gestire tutte le possibili request provenienti dalle View. I page controller possono essere semplici servlet, ma spesso sono implementate utilizzando il pattern Command [6.4] della GOF. Il class diagram successivo mostra come implementare il pattern del Front Controller. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 27/42 FUNZIONE QUALITÀ E SICUREZZA Figura 2 - Front Controller: Class Diagram L’uso del front controller non è esente da costi. Poiché il suo codice è eseguito per ogni possibile request, la sua implementazione deve essere il più efficiente possibile. La maggior parte dei sistemi, gli application server o altri framework, fornisce dei metodi per effettuare efficientemente certe funzioni di uso comune, come il logging o la gestione degli utenti. Il loro uso nel front controller consente di ottenere maggiori performance, rispetto a implementazioni ad hoc, ma questo si riflette negativamente sulla flessibilità del codice. 9.2.2 Service to Worker Il pattern Service to Worker si basa sul pattern Front Controller. L’obbiettivo di questo pattern è disaccoppiare maggiormente le componenti partecipanti di base del pattern MVC. In questo scenario, il front controller, ovvero il punto che centralizza la gestione delle richieste, è il service. Come il front controller, il service delega l’aggiornamento del model a classi specifiche per ogni pagina, chiamate worker. Nel pattern del Service to Worker un oggetto, chiamato dispatcher, ha il compito di gestire i worker e le view. Il dispatcher contiene la logica per la selezione delle pagine, e di conseguenza, per la scelta del worker. L’utilizzo del dispatcher consente di disaccoppiare il funzionamento dell’applicazione da quello del front controller. In ambiente J2EE il service è rappresentato dal controller, mentre i worker sono di solito chiamate action. Nella sua implementazione più semplice il dispatcher è un metodo del controlller che prende in input alcuni dei parametri, dalla request dell’utente o di altro genere (pagina corrente, permessi, validità delle informazioni immesse, ecc.) e li utilizza per selezionare l’action appropriata o una view. In alternativa può essere realizzato da una classe separata, e nel caso ideale, configurata tramite un file a runtime. Un dispatcher usa in insieme di action per effettuare l’aggiornamento del model. Ogni action implementa una ed una sola operazione sul model. Dato che la sequenza di navigazione è incapsulata nella logica del dispatcher, le action non sono responsabili della selezione della view. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 28/42 FUNZIONE QUALITÀ E SICUREZZA Come nel pattern del front controller, le action sono implementate usando il pattern Command [6.4] della GOF. Il diagramma delle classi per il pattern del Service to Worker è mostrato di seguito. Figura 3 - Service to Worker: Class diagram 9.3 View Avanzate 9.3.1 View Helper L’obbiettivo del pattern View Helper è ridurre la specializzazione delle view e il codice java in esse contenuto. Le attività che coinvolgono una view sono due: • La preparazione della view: la fase in cui ogni request è risolta da una specifica elaborazione che identifica la view appropriata; • La creazione della view: fase in cui la view riceve i contenuti da presentare dal model e li mostra in un formato appropriato. In applicazioni enterprise, i dati forniti dal model alle pagine JSP sono notevolmente strutturati e differenziate secondo la request inviata dall’utente. Ciò porta da un lato ad avere una elaborata logica di formattazione nelle pagine JSP, dall’altro ad un incremento del numero di pagine della View. Il View Helper agisce da intermediario tra il model e la View; legge infatti i dati di business specifici di un request e li trasforma, alcune volte direttamente in HTML, ma più spesso in un data model intermedio. In questo modo invece di view contenenti codice specializzato per trattare con model particolari, le pagine JSP includono chiamate generiche all’Helper. Il View Helper aumenta la riusabilità del codice in due modi: • riducendo l’ammontare di codice specializzato nelle view le rende riusabili per data model differenti. • poiché incapsula le interazioni con il model, l’Helper può essere esso stesso riusato. La figura seguente mostra le interazioni tra View, Model e Helper tramite un sequence diagram. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 29/42 FUNZIONE QUALITÀ E SICUREZZA Figura 4 - View Helper: Sequence Diagram 9.3.2 Composite View Obiettivo del pattern Composite View è quello di costruire una view modulare a partire da componenti atomiche combinate insieme. Le View tendono ad avere molti elementi ripetuti. La struttura della pagine di molti siti, per esempio, comprende un header, una navaigation area e un footer. Per ottenere pagine con un aspetto consistente vorremmo essere in grado di specificare la struttura delle pagine in un template, variandone il contenuto in base all’applicazione o alla pagina. Vorremmo inoltre applicare questo concetto in modo ricorsivo, separando il template di ogni area delle nostre pagine in template e contenuto. Il pattern del Composite View pattern soddisfa entrambi questi requisiti. Il pattern del Composite View si basa sul pattern Composite della GOF [5.2]. L’idea che sta alla base del pattern è molto semplice, trattare gli oggetti come un gerarchia ad albero in cui ogni padre espone la stessa gerarchia dei figli. Applicare questo concetto ad una view, significa pensare ad ogni pagina come composta da un certo numero di elementi. Ciascun elemento può essere una foglia della gerarchia o un contenitore di altri elementi. Il pattern del composite view può essere implementato tramite numerose strategie. La View di una applicazione web ha addirittura un supporto nativo per un livello base di composizione. Infatti sia le JSP che le Servlet possono essere utilizzate come view, ma possono anche essere dei container. La direttiva JSP include e il metodo RequestDispatcher.include( ) per le servlet permettono l’inclusione di altre JSP, servlet o pagine HTML statiche. Ma mentre il meccanismo di inclusione delle servlet è molto flessibile, la direttiva include delle JSP non lo è. Infatti, l’inclusione di una pagina richiede l’introduzione della sua URL nella pagine padre, generando uno stretto accoppiamento tra la pagina padre e le pagine figlie. Per aumentare la flessibilità nelle JSP, la gestione della view può essere implementata usando componenti JavaBean. La decisione sul layout della pagina può essere basata sui ruoli dell’utente o su policy di sicurezza, rendendone la logica molto più potente che con l’uso della direttiva include standard. Il codice seguente mostra un esempio di implementazione del pattern seguendo la strategia JavaBean View Managment. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 30/42 FUNZIONE QUALITÀ E SICUREZZA Esempio 25 <%@page import="corepatterns.compositeview.beanhelper.ContentHelper" %> <% ContentHelper personalizer = new ContentHelper(request); %> <table valign="top" cellpadding="30%" width="100%"> <% if (personalizer.hasWorldNewsInterest() ) { %> <tr> <td><jsp:getProperty name="feeder" property="worldNews"/></td> </tr> <% } if ( personalizer.hasCountryNewsInterest() ) { %> <tr> <td><jsp:getProperty name="feeder" property="countryNews"/></td> </tr> <% } if ( personalizer.hasCustomNewsInterest() ) { %> <tr> <td><jsp:getProperty name="feeder" property="customNews"/></td> </tr> <% } if ( personalizer.hasAstronomyInterest() ) { %> <tr> <td><jsp:getProperty name="feeder" property="astronomyNews"/></td> </tr> <% } %> </table> Una valida alternativa a questa strategia è rappresentata dall’uso di custom tag (si veda "Advanced JavaServer Pages" [Geary][2.1.6]) Le due strategie sono funzionalmente equivalenti. Si può scegliere di utilizzare l’una o l’altra indifferentemente tenendo presente che la strategia JavaBean View Managment richiede l’utilizzo di scriptlet all’interno di pagine JSP, soluzione questa poco elegante, ma comporta oneri implementativi minori. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 31/42 FUNZIONE QUALITÀ E SICUREZZA 10 Business tier pattern 10.1 Composite Entity Scopo del pattern Composite Entity è fornire un’interfaccia di interazione coarse-grain con gli oggetti che implementano il livello di persistenza applicativo. Nelle applicazioni in piattaforma J2EE uno dei task maggiormente ricorrenti consiste nella mappatura di uno schema dati relazionali in entity bean. L’approccio più immediato, che alle volte si definisce “AnEntity-Bean-for-Everything”, consiste nel creare un entity bean per ogni tabella presente nel database. Ciò porta alla definizione di numerosi entity bean fine-grain. Occorre tuttavia considerare che i client –quali le applicazioni, le pagine JSP o le servlet– possono accedere agli entity bean tramite le loro interfacce remote, sebbene sia considerata pratica di buona programmazione accedere agli EJB di entità solo attraverso stateless session beans locali [2.2.2]. In questo modo, ogni potenziale invocazione da parte del client viaggia attraverso la rete usando gli stub e gli skeleton del bean. Quando gli entity bean sono fine grain i client tendono ad invocare più metodi dell’entity, generando un elevato overhead di rete. L’uso del pattern del Composite Entity permette di modellare, rappresentare e gestire un set d’oggetti persistenti interrelati piuttosto che rappresentarli come entity bean a grana fine. I partecipanti al pattern sono: • CompositeEntity: è un entity bean coarse-grained. Il CompositeEntity può essere un CoarseGrainedObject o mantenere un riferimento ad un CoarseGrainedObject, ma nella strategia più comunemente usata il CompositeEntity è esso stesso il CoarseGrainedObject. • CoarseGrainedObject: è un oggetto che ha un proprio ciclo di vita e gestisce proprie relazioni con altri oggetti. • DependentObject1, DependentObject2, …, DependentObjectN: sono oggetti che dipendono dal CoarseGrainedObject ed il loro ciclo di vita è gestito dal CoarseGrainedObject. Un DependentObject può contenere oltri DependentObject fino a formare un albero di complessità arbitraria con radice rappresentata dal CoarseGrainedObject. Esempio 26 Riprendendo l’esempio riportato in [2.1.8], supponiamo di voler definire gli enity per l’acesso allo schema relazionale mostrato in figura: Figura 5 - Composite Entity: Schema ER Usando il pattern Composite Entity definiamo due entity bean: il bean Patient e il bean HospitalVisit. Il bean HospitalVisit contiene gli oggetti che rappresentano la data della visita medica, il medico, e tutte le azioni svolte durante la visita. L’entity bean Patient contiene invece un oggetto Address ed ha una relazione uno-a-molti con un set di HospitalVisit entity bean. Il diagramma delle classi proposto è il seguente: Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 32/42 FUNZIONE QUALITÀ E SICUREZZA Figura 6 - Composite Entity: Class Diagram 10.2 Business Delegate Scopo del pattern Business Delegate è ridurre l’accoppiamento tra i client del presentation tier e i servizi esposti dal business tier. Il Business Delegate nascondere i dettagli dell’implementazione sottostante come le lookup e i dettagli di accesso agli EJB. Il Presentation Tier spesso usa direttamente i servizi del livello di business dell’applicazione. Come risultato il livello di presentazione è vulnerabile ai cambiamenti nell’implemantazione dei servizi di business: quando l’interfaccia dei servizi di business cambia, devono cambiare anche i servizi del presentation tier che li utilizzano. Inoltre, quando il presentation tier usa le API del livello di business direttamente, senza meccanismi di caching o di aggregazione, le chiamate attraverso le rete possono diventare eccessive e dannose per le performance. Tuttavia il maggior vantaggio consiste nel fatto che il Business Delegate agisce come livello di astrazione del business tier lato client; nasconde, quindi, l’implementazione dei servizi di business. Questo riduce il numero di modifiche che devono essere fatte sul livello di presentazione quando cambiano le API del business layer o la sua implementazione. Per esempio i servizi di lookup e naming diventano trasparenti ai client. Il Business Delegate nasconde inoltre la gestione delle eccezioni provenienti dai servizi di business, come, per esempio, le eccezioni java.rmi.Remote, le eccezioni Java Messages Service (JMS) e così via. Il Business Delegate può intercettare tali eccezioni service-level e generare al loro posto delle eccezioni application-level, più facili da gestire per i client o eseguire in modo trasparente per i client le azioni per recovery di situazioni anomale. Altro beneficio è dato dal fatto che il business delagate può cachare i risultati e i riferimenti a servizi remoti del business layer. Un Business Delegate usa un componente chiamato Lookup Service. Il Lookup Service si occupa di nascondere i dettagli implementativi del business tier e il codice di lookup dei servizi. Il Lookup Service può essere scritto come parte del Delegate, ma è preferibile implementarlo come un componente separato. I partecipanti al pattern sono: • BusinessDelegate: il ruolo del BusinessDelegate è quello di fornire controllo e protezione per il servizio di business. Il BusinessDelegate può esporre due tipi di costruttori al client: uno che istanzia il BusinessDelegate senza nessun ID, mentre l’altro lo istanzia con un ID, dove l’ID è una stringa che identifica il riferimento ad un oggetto remoto come un EJBHome o un EJBObject. Quando è inizializzato senza un ID, il BusinessDelegate richiede il riferiemtno al servizio al LookupService, che ritorna un Service Factory, come un EJBHome. Quando inizializzato con un ID, il BusinessDelegate lo usa per riconnetersi allo stesso BusinessService. • LookupService: il BusinessDelegate usa il LookupService per localizzare il BusinessService. Il LookupService incapsula i dettagli implementativi della lookup al BusinessService. • BusinessService: il BusinessService è un componente business-tier, come un enterprise bean o un componente JMS, che fornisce il sistema richiesto al client. La figura seguente mostra il class diagram per il pattern del Buisiness Delegate. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 33/42 FUNZIONE QUALITÀ E SICUREZZA Figura 7 - Business Delegate: Class Diagram 10.3 Session Facade La maggior parte della complessità di un sistema distribuito è localizzata negli Enterprise JavaBean. I bean, tuttavia, non eliminano tale complessità, ma la nascondono incapsulandola in oggetti di più facile utilizzo. Per esempio, il fatto che gli sviluppatori non si occupano di scrivere il codice per trasportare i dati su una connessione TCP/IP non significa che i dati non viaggiano attraverso la rete. In una architettura basata su EJB, le performance rappresentano la sfida maggiore da affrontare, in particolar modo quando la potenza elaborativa dell’hardware a disposizione è limitata. Se una applicazione client ha necessità fare accessi multipli a entity bean per portare a termine una transazione applicativa, effettua una chiamata sulla rete ogni volta che deve ottenere un nuovo bean o chiamare un metodo dell’EJB. Le conseguenze di tutto ciò sulle performance sono potenzialmente enormi. Il pattern del Composite Entity [8.1] riduce il numero di entity bean nel sistema e rende più semplice la loro gestione; tuttavia non affronta il più ampio problema delle performance. A peggiorare le cose, anche il miglior design di entity bean usa più di un bean per un singolo caso d’uso e un client che vuole modificare più di un entity bean nel corso di una transazione deve gestire il controllo della transazione attraverso le API JTA o un altro meccanismo. In alternativa, la logica di business di uno specifico caso d’uso può essere costruita all’interno di un singolo bean, ma questo approccio ha come risultato un design del bean eccessivamente complesso. Il pattern del Business Delegate [8.2] aiuta ad organizzare il livello di business logic dell’applicazione in modo più appropriato e facile da usare, ma non controlla il numero di chiamate alla rete, né semplifica la gestione delle transazioni. L’uso del pattern del session facade (ovvero di session bean come “interfaccia” verso i client) permette di nascondere la complessità delle interazioni tra gli oggetti del business-tier che partecipano al raggiungimento di uno stesso obbiettivo. Il Session Facade gestisce gli oggetti business esponendo verso i client un livello di accesso a servizi coarse-grained. Il session facade astrae le interazioni dei sottostanti oggetti di business e fornisce un service layer che espone solo le interfacce richieste. In questo modo nasconde le complesse interazioni tra i partecipanti. Il session facade ne gestisce anche il ciclo di vita, creandoli, localizzandoli (tramite lookup), modificandoli e distruggendoli quando richiesto dal workflow. In applicazioni molto complesse, il session facade può delegare la gestione del ciclo ad un oggetto separato, per esempio un Service Locator [8.4]. I partecipanti al pattern sono: • Client: rappresenta il client per il session facade, che necessita di accedere ai servizi di business. Questo client può essere un bean nello stesso business tier o un business delegate [8.2] in un altro tier. • SessionFacade: è implementato da un session bean. Il session facade gestisce le relazioni tra i numerosi BusinessObjects e fornisce un livello di astrazione per il client e offre un accesso coarsegrained ai BusinessObject. • BusinessObject: il BusinessObject può essere un session bean, entity bean o un DAO. Un BusinessObject fornisce dati e/o servizi. Il SessionFacade interagisce con molte istanze di BusinessObject per fornire al client un servizio coarse-grained. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 34/42 FUNZIONE QUALITÀ E SICUREZZA La figura seguente mostra il class diagram per il pattern del Session Facade. Figura 8 - Session Facade: Class Diagram Approfondimenti 1 Abbiamo visto come il design di un session facade è strettamente legato ai casi d’uso individuati in fase di analisi. Ma quale è la strategia migliore per individuare i session facade tramite lo studio dei casi d’uso? Mappare ogni use case in un session facade porterà alla definizione di troppi facade. Ciò impedirà di raggiungere l’obiettivo che ci si era posto in principio: avere pochi session bean caorse-grained. Dato che la fase di design è successiva al consolidamento della fase di analisi, è sempre possibile cercare di migliorare il design riducendo il numero di session bean raggruppandoli secondo una qualche partizione logica. Per esempio, in una applicazione per la gestione di una banca, è possibile raggruppare le interazioni relative alla gestione di un conto in un singolo facade. I casi d’uso “Crea un Nuovo Conto”, “Cambia le Informazioni del Conto”, “Visualizza le Informazioni del Conto” e così via hanno tutti a che fare con un oggetto coarse-grained Conto. Creare un session facade per ciascun di questi casi d’uso non è quindi raccomandabile. È invece preferibile raggruppare tutte le funzioni richieste per completare gli use case in un singolo session facade chiamato, per esempio SessionFacadeConto. In questo caso, il session facade diventa un controller con una granularità molto alta e con metodi ad alto livello che possono eseguire le interazione individuate nella prima fase di design (cioè metodi del tipo creaNuovoConto, modificaConto, getInfoConto). In conclusione si raccomanda di progettare i Session Facade aggregando gruppi di interazioni correlate. In questo modo è possibile ottenere una applicazione con un numero minimo di facade e godere appieno dei benefici del pattern. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 35/42 FUNZIONE QUALITÀ E SICUREZZA 10.4 Service Locator I client J2EE interagiscono con componenti che forniscono servizi, quali per esempio gli Enterprise JavaBeans (EJB) e i componenti JMS (Java Message Service). Per interagire con questi componenti i client devono localizzare tali componenti. Per esempio, un EJB client deve localizzare l’interfaccia home del bean per usarla in seguito per trovare, creare o rimuovere uno o più enterprise bean. Tutte le applicazioni su piattaforma Java 2 Enterprise Edition (J2EE) usano le API JNDI per fare la lookUp e creare componenti EJB e JMS; in altre parole, localizzare un oggetto accessibile via JNDI è un task comune a tutti i client che usano un determinato servizio. Ciò porta ad una replicazione del codice necessario ad effettuare la lookup dei servizi. Inoltre, cerare un oggetto initialContext JNDI e fare la lookUp di un oggetto EJB home richiede l’impiego di un notevole numero di risorse. L’impatto sulle performance di più client che richiedono ripetutamente lo stesso oggetto EJB home, può essere molto negativo. Occorre ancora sottolineare che il processo di look up e di creazione coinvolge l’implementazione di context factory specifici per ogni fornitore di application server. Ciò introduce una dipendenza dal vendor nell’applicazione client che compie una lookUp JNDI. L’uso del pattern del Service Locator permette di astrarre l’uso di tutte le chiamate JNDI e nascondere la complessità della creazione dell’initial context e della lookup di oggetti EJB home. I client possono riusare l’oggetto Service Locator per ridurre la complessità del codice, fornire un singolo punto di controllo e migliorare le performance fornendo delle funzionalità di caching. I partecipanti al pattern del Service locator sono: • Client: è il client del service locator. Il client è tipicamente un oggetto che richiede l’accesso ad un servizio del business teir, come ad esempio il Business Delegate [8.2]. • ServiceLocator: Il ServiceLocator astrae le API del servizio di lookup, la dipendenza dal vendor, la complessità della lookup e della creazione degli oggetti del business, e fornisce al client una interfaccia semplificata. Ciò riduce la complessità del client. In aggiunta, lo stesso client o altri client possono riusare il ServiceLocator. • InitialContext: La classe InitialContext è il punto iniziale per la creazione di oggetti JNDI: dopo avere istanziato un oggetto di questa classe al suo interno possono essere creati oggetti e contesti. • ServiceFactory: l’oggetto ServiceFactory rappresenta un oggetto che provvede alla gestione del ciclo di vita di un oggetto BusinessService. L’oggetto ServiceFactory per un bean di tipo enterprise è l’EJBHome. • BusinessService: l’oggetto BusinessService è gestito dal ServiceFactory che ne può effettuare la lookup, la creazione o la rimozione. Per un EJB l’oggetto BusinessService è lo stesso enterprise bean. La figura successiva mostra il class diagram per il pattern del Service Locator: Figura 9 - Service Locator: Class Diagram Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 36/42 FUNZIONE QUALITÀ E SICUREZZA Il codice seguente mostra, invece, una possibile implementazione della classe ServiceLocator. Esempio 27 import import import import import import java.util.*; javax.naming.*; java.rmi.RemoteException; javax.ejb.*; javax.rmi.PortableRemoteObject; java.io.*; public class ServiceLocator { private static ServiceLocator me; InitialContext context = null; private ServiceLocator() throws ServiceLocatorException { try { context = new InitialContext(); } catch(NamingException ne) { throw new ServiceLocatorException(...); } } // Ritorna l’istanza della classe ServiceLocator public static ServiceLocator getInstance() throws ServiceLocatorException { if (me == null) { me = new ServiceLocator(); } return me; } public EJBObject getService(String id) throws ServiceLocatorException { if (id == null) { throw new ServiceLocatorException(...); } try { byte[] bytes = new String(id).getBytes(); InputStream io = new ByteArrayInputStream(bytes); ObjectInputStream os = new ObjectInputStream(io); javax.ejb.Handle handle = (javax.ejb.Handle)os.readObject(); return handle.getEJBObject(); } catch(Exception ex) { throw new ServiceLocatorException(...); } } protected String getId(EJBObject session) throws ServiceLocatorException { try { javax.ejb.Handle handle = session.getHandle(); ByteArrayOutputStream fo = new ByteArrayOutputStream(); ObjectOutputStream so = new ObjectOutputStream(fo); so.writeObject(handle); so.flush(); so.close(); return new String(fo.toByteArray()); } catch(RemoteException ex) { throw new ServiceLocatorException(...); } catch(IOException ex) { Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 37/42 FUNZIONE QUALITÀ E SICUREZZA throw new ServiceLocatorException(...); } return null; } public EJBHome getHome(String name, Class clazz) throws ServiceLocatorException { try { Object objref = context.lookup(name); EJBHome home = (EJBHome) PortableRemoteObject.narrow(objref, clazz); return home; } catch(NamingException ex) { throw new ServiceLocatorException(...); } } } 10.5 Service Activator Gli EJB costituiscono la scelta più appropriata quando una applicazione distribuita necessita di una elaborazione sincrona. Tuttavia alcune applicazioni potrebbero aver bisogno di meccanismi asincroni di calcolo, sia perché non sono “interessati” alla risposta del server o perché non possono attendere il completamento di operazioni particolarmente costosi in termini di tempo. In questi casi i bean potrebbero non essere adatti in quanto la lookup dell’oggetto home di un bean e l’invocazione dei metodi di business su un reference remoto, sono chiamate sincrone: il client che accede al bean deve cioè attendere fin quando il servizio remoto non ritorna una qualche risposta. Inoltre occorre tener conto che specifiche EJB consentono al container EJB di passivare un entreprise java bean salvandolo in un dispositivo per la memoria di massa. Solo con le specifiche EJB 2.0 che hanno introdotto i message-driven bean, gli EJB container sono in grado di offrire meccanismi per l’invocazione asincrona di servizi. Nonostante ciò le nuove specifiche non permettono l’invocazione asincrona di enterprise bean, come gli stateful o gli entity beans. Invocare in modo asincrono un servizio business che offre solo una interfaccia sincrona rappresenta quindi una sfida per lo sviluppatore. L’uso del pattern del Service Activator permette di ricevere le request e i messaggi asincroni dei client. Alla ricezione di un messaggio, il Service Activator localizza e invoca l’opportuno metodo di business per soddisfare le richieste in modo asincrono. I partecipanti al pattern sono: • Client: Il client che richiede un servizio di elaborazione asincrona ai businessObject partecipanti al pattern. Il client può essere qualsiasi tipo di applicazione che ha la possibilità di creare e mandare messaggi JMS. • Request: l’oggetto Request è il messaggio creato dal client e inviato al ServiceActivator attraverso un Message Oriented Middleware (MOM). In accordo con le specifiche JMS, la classe Request deve implementare l’interfaccia javax.jms.Message. Le API JMS forniscono vari tipi di messaggi quali per esempio i TextMessage, ObjectMessage e cosi via. • ServiceActivator: Il ServiceActivator è la classe principale del pattern. Implementa l’interfaccia javax.jms.MessageListener definita dale specifiche JMS. Il ServiceActivator implementa il metodo onMessage() che è invocato al sopraggiungere di un nuovo messaggio. Il ServiceActivator effetta unmarshaling del messaggio (l’oggetto Request) per scegliere il servzio da invocare e i parametri della chiamata. • BusinessObject: è l’ggetto target che il client ha necessità di accedere in modalità asincrona. Può essere un session o un entity bean o anche un web service. La figura successiva mostra il class diagram per il pattern del Service Activator: Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 38/42 FUNZIONE QUALITÀ E SICUREZZA Figura 10 - Service Activator: Class Diagram Il ServiceActivator può usare I servizi di un Service Locator [8.4] per localizzare i componenti del business tier. 11 Communication Pattern Il business tier, la parte più critica di una applicazione enterprise, non esiste isolata dal resto del sistema: il livello di business necessita di una interfaccia (o nel caso generale di più interfacce) verso il modo esterno. In questo capitolo si tratterà il problema di gestire la comunicazione tra i tier usando i pattern per il trasferimento dei dati. 11.1 Data Transfer Object Pattern (DTO) Le applicazioni Java 2 Enterprise Edition implementano componenti di business server side come session bean and entity bean. È frequente il caso in cui un metodo di questi componenti ritorna dei dati al client. Spesso il client chiama i metodi getter più volte fino ad ottenere il set di valori di interesse. I session bean rappresentano servizi di business non condivisi tra gli utenti. Un session bean fornisce servizi coarse-grain quando implementa il pattern Session Facade [8.3]. Gli entity bean sono invece oggetti multiuser e transazionali che rappresentano dati persistenti. Un entity bean espone i valori dei sui attributi mettendo a disposizione dei client metodi accessori (i cosiddetti metodi getter). Ogni chiamata ai metodi fatta ad un oggetto del business layer, sia esso un entity o un session, è potenzialmente una chiamata remota. Anche se qualche vendor di application server implementa meccanismi per ridurre l’overhead di rete quando client e server condividono la stessa JVM, sistema operativo o macchina fisica, l’uso di questi metodi remoti può far degradare in modo significativo le performance del sistema. In conclusione l’uso di chiamate multiple per ritornare il valore di un singolo attributo è una pratica poco efficiente per ottenere dati da un Enterprise Java Bean. L’uso del pattern del Transfer Object consente di incapsulare i dati del business layer rendondo più efficiente il loro trasferimento tra server e clients in quanto un unico singolo metodo è invocato per ottenere il Transfer Object. Quando un client richiede ad un Enterprise Java Bean i dati di business, l’EJB può costruire un Transfer Object, popolarlo con i valori opportuni e passarlo per valore al client. Quando un bean usa un Transfer Object, il client effettua una singola chiamata ad un metodo remoto ottenendo un oggetto complesso piuttosto che numerose chiamate per ottenere dati elementari. Una volta ricevuto il Transfer Object, il client ne può usare i metodi accessori (getter) per ottenere i valori elementari richiesti. Dato che il Transfer Object è passato per valore, le chiamate ai suoi metodi sono chiamate locali, quindi senza overhead di rete. I partecipanti al pattern del Transfer Object sono: • Client: rappresenta il client dell’enterprise bean. Il client può essere una applicazione utente, un business delegate [8.2] o un altro oggetto del business layer. • BusinessObject: è l’oggetto che implementa la logica di business; può essere un bean, di tipo entity o session, o un Data Access Object (DAO) [10.1]. Il BusinessObject si occupa della Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 39/42 FUNZIONE QUALITÀ E SICUREZZA • creazione del Transfer Object e di restituirlo al client. Il BusinessObject può anche ricevere dati da un’altro client sotto forma di un Transfer Object ed usarli per effettuare un aggiornamento. TransferObject: è un oggetto java serializzabile. Può essere implementato da una classe java fornita di un costruttore che accetta tutti gli attributi che l’oggetto è destinato a trasportare. Per una maggiore semplicità implementativa, si può scegliere di concedere una deroga ai canoni della programmazione ad oggetti e definire i membri del Transfer Object come pubblici. Questa pratica, anche se poco ortodossa e spesso sconsigliabile, evita la necessità di definire i metodi getter e setter. Se le variabili membro non sono pubbliche e non si definiscono i metodi setter, si impedisce che i valori contenuti nel Transfer Object possano essere modificati. La figura successiva mostra il digramma delle classi di una possibile implementazione del pattern del DTO. Figura 11 - Data Transfer Object: Class Diagram 12 Data Access Pattern Il meccanismo di persistenza più usato in ambito J2EE è rappresentato dai database relazionali o RDBMS. Per questa ragione, in questo capitolo l’attenzione sarà focalizzata sui pattern da applicare ai database relazionali. Il linguaggio JAVA, sia nella sua versione standard che all’interno del paradigma J2EE, è fortemente impregnato dalla tecnologia dei database. Gli enterprise java bean sono stati progettati pensando ad un backend con a disposizione un database relazionale, le API JDBC di java standard edition forniscono un potente meccanismo di basso livello per connettere qualsiasi classe java ad un qualsiasi database esterno. Altre tecnologie, come Java Data Object o i framework per l’O/R mapping, forniscono un alto livello di astrazione dello strato di persistenza dei dati, gestendo il mapping tra classi java e tabelle relazionali e i meccanismi di accesso ai dati. Naturalmente esistono altri meccanismi per gestire la persistenza dei dati con java: i database non sono l’unica soluzione. Le applicazioni più semplici possono immagazzinare i dati in file su disco, in formato testo, binario o anche XML. Gli stessi oggetti Java possono essere serializzati direttamente sui supporti di memoria di massa per essere ricaricati in un secondo momento, sebbene ciò renda difficoltoso sia l’accesso ai dati da parte delle applicazioni basate su altre tecnologie che quello diretto degli esseri umani. Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 40/42 FUNZIONE QUALITÀ E SICUREZZA 12.1 Data Access Object Pattern (DAO) Qualsiasi tipo di livello di persistenza scegliamo di usare, esistono una serie di problematiche che nascono quando decidiamo di includere la logica di persistenza direttamente nei componenti di business. Se un oggetto del business tier è responsabile della gestione della persistenza, significa che abbiamo assegnato ad una stessa classe la responsabilità di due azioni distinte; anche se questo non è intrinsecamente errato, può diventare problematico in molti casi. Infatti, l’intero layer di persistenza deve essere implementato prima che un oggetto di business possa essere testato del tutto. Inoltre gli oggetti del business tier diventano strettamente legati al particolare schema del livello di persistenza. Infine il codice all’interno di un oggetto del business diventa molto più complesso e difficile da capire. Problematiche analoghe si possono riscontrare quando si ha a che fare con un servizio esterno, come nel caso di una comunicazione Business To Business (B2B), o quando dobbiamo gestire l’accesso ad un repository come un database LDAP o ad un servizio di business via CORBA Internet Inter-ORB Protocol (IIOP). Questi sistemi si definiscono nel loro insieme data source. L’uso del pattern Data Access Object (DAO) permette di astrarre e incapsulare tutti gli accessi al data source. L’oggetto DAO gestisce la connessione con il data source per estrarre e/o immagazzinare i dati. Gli oggetti DAO implementano i meccanismi di accesso richiesti per lavorare con il data source. Il DAO nasconde del tutto ai suoi client i dettagli implementativi di accesso al data source. Poiché l’interfaccia esposta dal DAO non cambia quando il sottostante data source cambia implementazione, questo pattern permette al Data Access Object di adattarsi a differenti schemi implementativi senza che questo abbia alcun effetto sui client o sui componenti del business layer. In definitiva il DAO agisce da adapter [5.3] tra i componenti del business tier e il data source. I partecipanti al pattern sono: • BusinessObject: rappresenta il client del dato. È l’oggetto che richiede l’accesso al data source per ottenere o immagazzinare informazioni. • DataAccessObject: è l’oggetto principale di questo pattern. Il DataAccessObject astrae l’implementazione dell’accesso ai dati per il BusinessObject per rendere l’accesso trasparente e indipendente dalla tecnologia utilizzata. • DataSource: è il data source con la sua implementazione attuale. Un data source può essere un database RDMS o OODBMS (database orientato agli oggetti), un repository XML, un file piatto e cosi via. Un data source può essere anche un altro sistema di (legacy/mainframe), un servizio B2B o qualsiasi altro tipo di repository (LDAP per esempio). • TransferObject: rappresenta il Transfer Object [9.1] usato per trasportare i dati. Il DataAccessObject puo usare il Transfer Object per ritornare i dati al client. Il DataAccessObject può anche ricevere dati dal client tramite un Transfer Object per aggiornare il data source. La figura successiva mostra il digramma delle classi per il pattern Data Access Object Figura 12 - Data Access Object: Class Diagram Di seguito viene invece fornito una possibile implementazione di un oggetto DAO: Esempio 28 import import import import java.util.*; java.sql.*; javax.sql.*; javax.naming.*; public class MyDataBaseObejctDAO { Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 41/42 FUNZIONE QUALITÀ E SICUREZZA public MyDataBaseObejctDAO findMyDataBaseObejct (long myDataBaseObejctId) { Connection con = null; PreparedStatement ps = null; ResultSet rs = null; MyDataBaseObejctDTO pMyDataBaseObejctDTO = null; try { con = getConnection( ); ps = con.prepareStatement ("select * from MyTable where id = ?"); ps.setLong(1, myDataBaseObejctId); rs = ps.executeQuery( ); if(!rs.next( )) return null; pMyDataBaseObejctDTO = new MyDataBaseObejctDTO( ); pMyDataBaseObejctDTO.id = myDataBaseObejctId; patient.myAttribute = rs.getString("myAttribute"); ... ps.close( ); rs.close( ); } catch (SQLException e) { e.printStackTrace( ); } finally { try { if(rs != null) rs.close( ); if(ps != null) ps.close( ); if(con != null) con.close( ); } catch (SQLException e) {} } return pMyDataBaseObejctDTO; } public void saveMyDataBaseObejct (MyDataBaseObejctDTO pMyDataBaseObejct) { // Persistence code goes here } public MyDataBaseObejctDTO createMyDataBaseObejct (MyDataBaseObejctDTO pMyDataBaseObejct) { // Creation code goes here } private Connection getConnection( ) throws SQLException { try { Context jndiContext = new InitialContext( ); DataSource ds = (DataSource)jndiContext.lookup ("java:comp/env/jdbc/MyDataSource"); return ds.getConnection( ); } catch (NamingException ne) { throw new SQLException (ne.getMessage( )); } } } Guida Metodologica Pattern Architetturali Java SQC609006 VER. 2 Pag. 42/42