Guida Metodologica Pattern Architetturali Java

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