
In questa lezione, presenteremo il lavoro effettuato dalla “Gang of Four” (GoF) sull’applicazione di modelli (patterns) nella progettazione di software utilizzando un linguaggio a oggetti. Questi modelli, costituiscono, secondo me, una parte fondamentale per i progettisti software, la parte iniziale di un lavoro che a volte può risultare lungo e complesso. D'altronde, spesso ci troviamo a risolvere problemi che si ripetono nel tempo, magari non allo stesso modo, ma in maniera simile a qualcosa che già è stato risolto in precedenza e questo, come capite bene, è un notevole vantaggio, che ci fa risparmiare tempo e fatica.
Quindi il riuso del software è il motivo principale per l’uso dei patterns,
ma non solo. Supponiamo, ad esempio, di aver progettato un software, di averlo
creato senza però aver rispettato un
criterio ben preciso e di decidere un giorno di volerlo ampliare. Ma non
vorremmo perdere molto tempo a capire il codice che avevamo scritto magari
qualche anno fa e poi trovarci molto probabilmente a rifare tutto perché alla
fine non c’abbiamo capito proprio nulla. Quindi i benefici principali sono la
riduzione del tempo per la ricerca delle soluzioni, e la riduzione del tempo
per la progettazione delle applicazioni. Beh, non poco direi.
Andiamo allora a trattare, in
linee generali per ragioni di spazio, i principali patterns sui 23 proposti dal
Gof. Se dopo questa lezione resterete affascinati da questi potenti strumenti,
potrete approfondire l’argomento consultando il libro “Design Patterns:
Elements of Reusable Object-Oriented Software” disponibile anche in
versione italiana.
Ps. Gli esempi che esporremo
vengono presentati in linguaggio Java, ma valgono per tutti i linguaggi a
oggetti (es. C++, C#, SmallTalk, ecc.).
Vi proporrò 6 patterns di uso più
comune e cercherò di convincervi che il loro uso vi semplificherà di molto la
vita vostra, degli altri e di quella del vostro software.
Iterator
Fornisce un modo di accedere sequenzialmente agli oggetti presenti in una
collezione, senza esporre la rappresentazione interna di questa.
Esempio
Il percorso di un viaggiatore è
rappresentato come una collezione ordinata di oggetti, dove ogni oggetto
rappresenta un luogo visitato. La collezione può essere implementata in base ad
un’array, una linked list o qualunque altra struttura. Una
applicazione sarebbe interessata ad accedere agli elementi di questa collezione
in una sequenza particolare, ma senza dover interagire direttamente con il tipo
di struttura interna.
Descrizione offerta dal pattern
Il pattern “Iterator”
suggerisce l’implementazione di un oggetto che consenta l’acceso e percorso
della collezione, e che fornisca una interfaccia standard verso chi è
interessato a percorrerla e ad accede agli elementi.
Strategy
Consente la definizione di una
famiglia d’algoritmi, incapsula ognuno e gli fa intercambiabili fra di loro. Questo
permette modificare gli algoritmi in modo indipendente dai clienti che fanno
uso di essi.
Esempio
La progettazione di una applicazione
che offre delle funzionalità matematiche, considera la gestione di una apposita
classe (MyArray) per la rappresentazione di vettori di numeri. Tra i metodi di
questa classe si ha definito uno che esegue la propria stampa. Questo metodo potrebbe
stampare il vettore nel seguente modo
(chiamato, ad es. MathFormat):
{ 12, -7, 3, … }
oppure di questo altro modo
(chiamato, ad. es. SandardFormat):
Arr[0]=12 Arr[1]=-7 Arr[2]=3 …
E’ anche valido pensare che
questi formati potrebbero posteriormente essere sostituiti da altri. Il
problema è trovare un modo di isolare l’algoritmo che formatta e stampa il
contenuto dell’array, per farlo variare in modo indipendente dal resto
dell’implementazione della classe.
Descrizione offerta dal pattern
Lo “Strategy” pattern
suggerisce l’incapsulazione della logica di ogni particolare algoritmo, in
apposite classi (ConcreteStrategy) che implementano l’interfaccia che consente
agli oggetti MyArray (Context) di interagire con loro. Questa interfaccia deve
fornire un accesso efficiente ai dati del Context, richiesti da ogni ConcreteStrategy,
e viceversa.
Visitor
Rappresenta una operazione da
essere eseguita in una collezione di elementi di una struttura. L’operazione può
essere modificata senza alterare le classi degli elementi dove opera.
Esempio
Si consideri una struttura che
contiene un insieme eterogeneo di oggetti, su i quali bisogna applicare la
stessa operazione, che però è implementata in modo diverso da ogni classe di
oggetto. Questa operazione potrebbe semplicemente stampare qualche dato dell’oggetto,
formattato in un modo particolare. Per esempio la collezione potrebbe essere un
Vector che ha dentro di se oggetti String, Integer, Double o altri Vector. Si
noti che se l’oggetto da stampare è un Vector, questo dovrà essere scandito per
stampare gli oggetti trovati ai suoi interni. Si consideri anche che
l’operazione ad applicare non è in principio implementata negli oggetti
appartenenti alla collezione, e che questa operazione potrebbe essere
ulteriormente ridefinita. Un approccio possibile sarebbe creare un oggetto con
un metodo adeguato per scandire collezioni o stampare i dati dell’oggetto. Questo approccio va bene se si vuole lavorare
con pochi tipi di dati, ma intanto questi aumentano il codice diventa una lunga
collezione di if…else.
Il problema è trovare un modo di
applicare questa operazione a tutti gli oggetti, senza includerla nel codice
delle classi degli oggetti.
Descrizione offerta dal pattern
La soluzione consiste nella
creazione di un oggetto (ConcreteVisitor), che è in grado di percorrere la
collezione, e di applicare un metodo proprio su ogni oggetto (Element) visitato
nella collezione (avendo un riferimento a questi ultimi come parametro). Per
agire in questo modo bisogna fare in modo che ogni oggetto della collezione
aderisca ad un’interfaccia (Visitable), che consente al ConcreteVisitor di
essere “accettato” da parte di ogni Element. Poi il Visitor, analizzando il
tipo di oggetto ricevuto, fa
l’invocazione alla particolare operazione che in ogni caso si deve eseguire.
Decorator
Aggiunge dinamicamente
responsabilità addizionali ad un oggetto. In questo modo si possono estendere
le funzionalità d’oggetti particolari senza coinvolgere complete classi.
Esempio
Si pensi ad un modello di oggetti
che rappresenta gli impiegati (Employee) di una azienda. Tra gli impiegati, ad
esempio, esistono gli Ingegneri (Engineer) che implementano le operazioni
definite per gli impiegati, secondo le proprie caratteristiche. Il sistema
comprende la possibilità di investire gli impiegati con delle responsabilità
aggiuntive, ad esempio, quando un impiegato diventa capoufficio (Administrative
Manager), oppure, quando viene assegnato alla direzione di un progetto (Project
Manager), essendo entrambe responsabilità non esculenti tra di loro. Questi
cambiamenti di tipologia di alcuni impiegati coinvolgono modifiche delle
responsabilità definite per gli oggetti, alterandone le esistenti o
aggiungendone nuove. Per questa ragione, sarebbe di interesse definire un modo
per aggiungere dinamicamente nuove responsabilità ad oggetto specifico,
eventualmente con la ulteriore possibilità di toglierle.
Descrizione offerta dal pattern
Il pattern suggerisce la
creazione di wrapper classes (Decorator) che racchiudono gli oggetti ai
quali si vuole aggiungere le nuove responsabilità. Questi ultimi oggetti,
insieme ai Decorator devono implementare una interfaccia comune, in modo che
l’applicazione possa continuare ad interagire con gli oggetti decorati. Per una
stessa interfaccia possono esserci più Decorator, ad esempio, per investire i
ruoli di capoufficio e di responsabile di un progetto. Il fatto che Decorator e
oggetti decorati implementino la stessa interfaccia, consente anche
l’applicazione di un Decorator ad un altro oggetto già decorato, ottenendo in
questo modo la sovrapposizione di funzioni (ad esempio, un impiegato potrebbe
essere investito come capoufficio e responsabile di un progetto
contemporaneamente).
Observer
Consente la definizione di
associazioni di dipendenza di molti oggetti verso di uno, in modo che se
quest’ultimo cambia il suo stato, tutti gli altri sono notificati e aggiornati
automaticamente.
Esempio
Ad un oggetto (Subject) vengono
comunicati diversi numeri. Questo oggetto decide in modo casuale di cambiare il
suo stato interno, memorizzando il numero ad esso proposto. Altri due oggetti
incaricati del monitoraggio dell’oggetto descritto (un Watcher e un
Psychologist), devono avere notizie di ogni suo singolo cambio di stato, per
eseguire i propri processi di analisi. Il problema è trovare un modo nel quale
gli eventi dell’oggetto di riferimento, siano comunicati a tutti gli altri
interessati.
Descrizione offerta dal pattern
Il pattern “Observer” assegna
all’’oggetto monitorato (Subject) il ruolo di registrare ai suoi interni un
riferimento agli altri oggetti che devono essere avvisati (ConcreteObservers)
degli eventi del Subject, e notificarli tramite l’invocazione a un loro metodo,
presente nella interfaccia che devono implementare (Observer). Questo pattern è
stato proposto originalmente dai GoF per la manutenzione replicata dello stato
del ConcreteSubject nei ConcreteObserver, motivo per il quale sono previsti una
copia dello stato del primo nei secondo, e la esistenza di un riferimento del ConcreteSubject
nel ConcreteObserver. Nonostante lo espresso nel parafo precedente, si deve
tenere in conto che questo modello può servire anche a comunicare eventi, in
situazioni nelle quali non sia di interesse gestire una copia dello stato del
Subject. Da un'altra parte si noti che non è necessario che ogni ConcreteObserver
abbia un riferimento al Subject di interesse, oppure, che i riferimenti siano
verso un unico Subject. Le Java API offrono un modello esteso in funzionalità,
allineato in questa direzione, che sarà l’approccio utilizzato in questo
esempio. Un’altra versione del pattern Observer, esteso con una gestione più completa
degli eventi, è implementato dentro l’ambiente di sviluppo G++ [16]. In questo
modello è consentita la registrazione del Observer presso il Subject, indicando
addizionalmente il tipo di evento davanti al quale l’Observer deve essere
notificato, e la funzione del Observer da invocare.
State
Consente ad un oggetto modificare
il suo comportamento quando il suo stato interno cambia.
Esempio
Si pensi ad un orologio che
possiede due pulsanti: MODE e CHANGE. Il primo pulsante serve per
settare il modo di operazione di tre modi possibili: “visualizzazione normale”,
“modifica delle ore” o “modifica dei minuti”. Il secondo pulsante, invece,
serve per accendere la luce del display, se è in modalità di visualizzazione
normale, oppure per incrementare in una unità le ore o i minuti, se è in
modalità di modifica di ore o di minuti.
In questo esempio, un approccio
semplicistico conduce all’implementazione del codice di ogni operazione come
una serie di decisioni:
operation buttonCHANGEpressed{
if( clockState = NORMAL_DISPLAY )
displayTimeWithLight();
else if( clockState = UPDATING_HOURS
)
hours++;
else if( clockState =
UPDATING_MINUTES )
minutes++;
...
}
Il problema di questo tipo di
codice è che si rende più difficile la manutenzione, perché la creazione di
nuovi stati comporta la modifica di tutte le operazioni dove essi sono testati.
Da un'altra parte non si tiene una visione dello stato, in modo di capire come
agisce l’oggetto (l’orologio in questo caso), a seconda del proprio stato,
perché questo comportamento è spezzato dentro l’insieme di operazioni
disponibili. Si vuole definire un meccanismo efficiente per gestire i diversi comportamenti
che devono avere le operazioni di un oggetto, secondo gli stati in cui si
trovi.
Descrizione offerta dal pattern
Il pattern “State” suggerisce
incapsulare, all’interno di una classe, il modo particolare in cui le
operazioni di un oggetto (Context) vengono svolte quando lo si trova in quello
stato. Ogni classe (ConcreteState) rappresenta un singolo stato possibile del
Context e implementa una interfaccia comune (State) contenente le operazioni
che il Context delega allo stato. L’oggetto Context deve tenere un riferimento
al ConcreteState che rappresenta lo stato corrente. Puoi Commentare l'articolo sul Forum Opinioni
Realizzato Da Gianfrix In Esclusiva Per TiempoLibreSite.com
|