Dipartimento di fisica “E


Strutture Riutilizzabili Un esempio: essere o avere?



Scaricare 1.8 Mb.
Pagina24/24
12.12.2017
Dimensione del file1.8 Mb.
1   ...   16   17   18   19   20   21   22   23   24

Strutture Riutilizzabili




Un esempio: essere o avere?


Nell'introduzione a questa seconda parte del corso, abbiamo gia' indicato le motivazioni fondamentali per lo sviluppo del linguaggio UML e per l'attenzione a strutture di codice, cioe' a strategie di programmazione, che possano essere utilizzate anche in programmi diversi da quelli per cui originariamente erano state costruite.

Vogliamo completare questa presentazione con un esempio molto semplice: consideraiamo una finestra che venga aperta sul terminale del computer, oppure un disegno o una casella di testo in un programma di grafica o di videoscrittura.

Una prima strutturazione delle classi e' quella indicata in figura 22: Windows eredita da Rettangolo, che definisce la forma e le proprieta' geometriche dell'oggetto di tipo Windows. In piu' Windows avra' altri attributi, relativi ad esempio alla forma della cornice o alla presenza di vari cursori. In questo caso ci si puo' esprimere affermando che Windows e' (is) un Rettangolo.

Che cosa accade se voglio aprire sul terminale una finestra con una forma diversa? L'oggetto Windows definito ora non e' piu' adatto al nuovo use case. Potrei ad esempio, sempre utilizzando la relazione di ereditarieta', definire varie Windows (WindowsRett, WindowsSqr, WindowsCrc) che ereditano dalle corrispondenti figure concrete Rettangolo, Quadrato e Cerchio e da una WindowsAbs che contiene i parametri che specificano alcune caratteristiche della Windows.



Un'altra scelta e' pero' possibile, ed e' quella riportata in figura 23: in questo secondo caso Windows ha un puntatore ad un oggetto di tipo Shape, che puo' essere istanziato utilizzando una delle classi concrete Quadrato, Cerchio o Rettangolo. In questo caso ci si puo' esprimere affermando che Windows ha (has) un Rettangolo.

Le due scelte di disegno sono in un certo senso equivalenti: preferire l’una o l’altra dipende dal problema concreto in esame, e dal ruolo che questa parte puo’ avere nell’insieme del programma.

Questo esempio molto semplice ha cercato di mostrare come la scelta del disegno piu’ efficiente non sia immediata, e derivi non soltanto dall’esame di una parte limitata di codice, ma dal ruolo che il codice ricopre nell’intero programma.





Figura 23: Schema dell'utilizzo di una relazione di Aggregazione: Windows has un Rettangolo.



Strutture di codice riutilizabili


Le strutture di codice riutilizzabili (Reusable Object-Oriented Software) permettono di “esportare” soluzioni di programmazione al di fuori del contesto primitivo nel quale sono state progettate. Le diverse soluzioni strategiche, una volta estratte dal loro precedente contesto, assumono caratteristiche di generalita’ che permettono il loro utilizzo in contesti anche molto diversi. La struttura astratta della Programmazione Object-Oriented rende inoltre molto efficiente questo meccanismo.

Le strutture ri-utilizzabili sono in sintesi una serie di classi, in genere molto piccole, che interagiscono tra loro, a livello di relazioni e di interazioni a run-time, che permettono di risolvere un problema specifico.

La riusabilita’ delle strutture astratte e il riutilizzo del codice, di cui abbiamo ampiamente trattato occupandoci di Ereditarieta’ e di Programmazione Generica (template) si situano pero’ a livelli diversi: nel secondo caso si tratta di un riutilizzo in un certo senso “locale”, in cui parti di codice connesse tra loro in quanto a relazioni tra classi o a strutturazione delle classi, vengono utilizzate piu’ volte senza la necessita’ di implementarle nuovamente. Nel primo caso invece si tratta di un riutilizzo di una strategia, anche se implementata naturalmente attraverso una serie di classi, che rende la prospettiva concettualmnte molto diverse e complementare rispetto alla precedente. Naturalmente anche le strutture riutilizzabili utilizzano relazioni di Ereditarieta’ e Programmazione Generica, e quindi implementano al loro interno il riutilizzo del codice.


Classificazione delle strutture di codice riutilizabili


Lo studio e l’utilizzo delle strutture di codice riutilizzabili puo’ trarre grande vantaggio da una classificazione in base agli aspetti fondamentali del programma ai quali fa riferimento.

Nel corso di un qualsiasi programma Orientato agli Oggetti, si inizia creando un oggetto, poi si pone l’attenzione sulla struttura degli oggetti ed infine e’ necessario valutare quale e’ il funzionamento dell’insieme. Questa schematizzazione puo’ aiutare a classificare le strutture di codice riutilizzabili in tre grandi gruppi:


i) Strutture Creazionali (Creational Patterns)

ii) Strutture Strutturali (Structural Patterns)

iii) Strutture di Funzionamento (Behavioral Patterns)
La prima classe di Strutture si occupera’ di realizzare una strategia per la creazione di oggetti, la seconda di esplicitare la struttura dell’insieme del programma, la terza del funzionamento dell’intero insieme.

Le strutture riutilizzabili possono essere descritte utilizzando alcuni parametri fissi di riferimento:


i) Il nome

ii) Il problema che vuole affrontare

iii) La soluzione che propone

iv) Le consequenze della sua applicazione

v) Collegamenti con altri pattern

vi) Confronto con altre soluzioni possibili


Nel seguito indicheremo la struttura di alcune strutture riutilizzabili, scelte tra le piu’ note e tra le piu’ semplici, descrivendone in maniera sintetica l’uso ed il funzionamento


Factory


La struttura denominata Factory e' di tipo creazionale e permette di disaccoppiare l'utilizzo di un oggetto dalla creazione dell'oggetto. Il programma indicato come Client in figura 24, cioe' l'insieme del programma, il main(), utilizza una certa classe di prodotti attraverso la classe AbstractProduct da cui ereditano le classi concrete. Ad esempio, AbstractProduct potrebbe essere la classe Shape e le classi ConcreteProduct1 e ConcreteProduct2 potrebero essere Cerchio e Rettangolo. La creazione di oggetti di tipo concreto non e' nota al Client, cioe' il Client non include le classi ConcreteProduct1 e ConcreteProduct2. Si richiede cioe' che il programma Client interagisca con i prodotti semplicemente attraverso l'interfaccia astratta, cioe' attraverso la classe base da cui ereditano tutte le classi concrete.

Il programma Client non sapra' allora come costruire gli oggetti concreti apparteneti alle classi

ConcreteProduct1 e ConcreteProduct2: il Client sa come utilizzarli, ma non sa come crearli! Per risolvere questo dilemma, e' possibile introdurre una classe specifica, che in figura 24 e' indicata come Factory, che si occupa di creare gli oggetti concreti ConcreteProduct1 e ConcreteProduct2. In questo modo il programma principale Client e' in relazione con Factory e con AbstractProduct, senza la necessita' di interagire con le classi dei prodotti concreti. Solo Factory interagisce con le classi concrete, si occupa di creare gli oggetti concreti e ne restituisce il puntatore al Client. Per il meccanismo dell'Ereditarieta' e del Polimorfismo descritto piu' volte nei capitoli precedenti, al Client sara' sufficiente conoscere un puntatore della classe AbstractProduct per accedere a tutti i metodi delle classi concrete ConcreteProduct1 e ConcreteProduct2, senza alcuna necessita' di includerle o di riferirsi esplicitamente ad esse.

Figura 24: Schema della struttura Factory.




Singleton


In alcuni casi puo' essere utile avere, all'interno di un programma, una sola istanza di un oggetto di una certa classe. Ad esempio, in un programma che si riferisce all'Universita' degli Studi Roma Tre, ci sara' una sola istanza della classe Dipartimento_di_Fisica: cioe' verra' creato un solo oggetto appartenente a tale classe. Questa situazione puo' essere facilmente realizzata utilizzando la struttura Singleton, di tipo creazionale, che permette di creare un solo oggetto di una certa classe e fornisce una modalita' comune di accesso a tale oggetto.

Lo schema di tale struttura e' riportato in figura 25: l'oggetto di tipo Singleton contiene un puntatore a se stesso, cioe' ad un oggetto della classe Singleton ( Singleton * _instance ; ). Quando un utilizzatore ha necessita' di accedere all'oggetto di tipo Singleton, invoca il metodo instance() della classe Singleton. Il metodo instance() controlla se il valore dell'attributo _instance e' nullo. L'attributo _instance nullo indica che l'oggetto di tipo Singleton non e' mai stato creato: in questo caso viene creato ( _instance = new Singleton() ; ) e viene restituito il puntatore _instance. L'attributo _instance diverso da zero indica invece che l'oggetto di tipo Singleton e' gia' stato creato in occasione di una precedente applicazione del metodo istance(): in questo caso viene semplicemente restituito il puntatore _instance.

In questo modo e' impossibile creare piu' istanze, cioe' piu' oggetti della classe Singleton: il costruttore Singleton() infatti e' di tipo privato, e quindi non puo' essere invocato dall'utente, che puo' utilizzare solo il metodo instance() il quale a sua volta controlla, con il meccanismo poco sopra descritto, che il puntatore restituito sia sempre lo stesso ed invoca il costruttore una sola volta, in occasione del primo utilizzo del metodo instance().

Per permettere un accesso globale al metodo instance() e per fare in modo che la variabile _instance assuma lo stesso valore tutte le volte che viene invocato il metodo instance(), e' necessario far precedere la variabile _instance ed il metodo instance() dalla parola riservata static. Maggiori dettagli di implementazione verranno mostrati nel capitolo successivo, dedicato ad alcune applicazioni delle strutture descritte in questo capitolo.



Figura 25: Schema della struttura Singleton.




Composite


La struttura denominata Composite, nota anche con il nome di Composto/Composito, viene utilizzata per modellare la struttura di un programma (Structural Pattern). Questa struttura viene utilizzata per descrivere la struttura di oggetti in cui un oggetto e' composto da altri oggetti. Immaginiamo ad esempio i moduli di elettronica distribuiti in un centro di ricerca: il Centro di Ricerca (ad esempio un oggetto della classe Building) conterra' dei Laboratori (Laboratory), i Laboratori conterranno degli armadi (Rack), gli armadi conterranno dei cestelli (Crate) i quali a loro volta conterranno dei moduli (Module).

Questa struttura a "scatole cinesi" potrebbe essere rappresentata come una serie successiva di relazioni di aggregazione. In questo caso pero' il codice verrebbe ripetuto piu' volte, poiche' la struttura tra due livelli successivi e' praticamente la stessa.

La struttura di tipo Composite, schematizzata in figura 26, fornisce una risposta adeguata a questo problema: ogni oggetto e' un componente (sia esso un Laboratory, un Building, un Crate, ecc.), per cui eredita dalla classe Component. In molti casi un oggetto e' anche un Composito, cioe' contiene altri oggetti: in questo caso esistera' una relazione di aggregazione tra gli oggetti della classe Composite e gli oggetti della classe Component, come indicato in figura 26. In altri casi invece, un Componente non contiene altri elementi, poiche' e' l'anello finale della catena: in questo caso apparterra' alla classe Leaf, che, come Composite, eredita da Component, ma che non e' in relazione di aggregazione con altri oggetti della classe Component.

L'attributo _children di Composite sara' ad esempio un contenitore STL con i puntatori degli oggetti di tipo Component di cui l'oggetto di tipo Composite e' costituito.

Attraverso lo schema fornito da questa struttura, il Client puo' trattare oggetti di tipo Component ed oggetti di tipo Composite attraverso l'unica classe Component, che funziona come una interfaccia.

Figura 26: Schema della struttura Composite (composto/composito).



Strategy


La struttura Strategy e' il primo esempio di struttura che si riferisce al Funzionamento di un Programma (Behavioral Patterns).

In alcuni casi puo' essere necessario utilizzare diverse implementazioni di uno stesso metodo. Ad esempio, in un programma di Fisica per la ricostruzione delle tracce in un rivelatore, puo' essere necessario utilizzare diversi algoritmi di fit, per valutare quale e' il migliore, oppure perche' ciascuno di essi si rivela il migliore in particolari condizioni.

Per poter disporre di questa flessibilita' senza dover includere tutti gli algoritmi concreti nel main(), cioe' nel programma Client, e' possibile utilizzare la struttura schematizzata in figura 27: il Client interagisce solo con la classe Strategy, dalla quale ereditano le classi ConcreteStrategyA ConcreteStrategyB ConcreteStrategyC, che implementano gli algoritmi concreti. L'invocazione del metodo prescelto avviene attraverso il meccanismo del Polimorfismo, poiche' Strategy * s fa riferimento ad una delle classi concrete.



Figura 27: Schema della struttura Strategy.


Observer


Un'altra struttura che si riferisce al funzionamento di un programma (Behavioral Patterns) e' l'Observer.


Nel corso di un programma puo' essere necessario scambiare informazioni, cioe' oggetti, tra varie sezioni del programma stesso. Quando un programma complesso e' diviso in varie sezioni, e' necessario mantenere la consistenza tra le varie parti e rendere il sistema il piu' aperto possibile all'aggiunta di nuove sezioni.

La struttura Observer fornisce la strategia per realizzare tutto questo, secondo lo schema mostrato in figura 28. L'oggetto di tipo Observer e' in attesa che vengano notificati gli oggetti di tipo Subject. Piu' tipi di osservatori, cioe' piu' istanze concrete di oggetti di tipo Observer sono naturalmente possibili. Quando lo stato del soggetto cambia, il nuovo stato viene notificato a tutti gli osservatori, cioe' a tutti gli oggetti della classe Observer. Questo viene fatto attraverso il metodo notify() di Subject , che a sua volta invoca i metodi update() di tutti gli oggetti di tipo Observer interessati agli oggetti di tipo Subject. A questo livello ed in questo momento avviene la comunicazione tra osservatori e oggetti, cioe' tra due sezioni del programma tra loro essenzialmente separate. Il metodo update() e' l'unico punto in cui cio' avviene, per cui sara' sufficiente che il metodo update() sia compatibile con la struttura degli oggetti di tipo Subject(). Eventuali variazioni della struttura degli oggetti di tipo Subject() puo' essre ininfluente per l'algoritmo implementato in update() oppure puo' richiedere alcune modifiche a tale metodo, limitate pero' ad un punto singolo e ben definito dell'intero programma.

Osserviamo infine come il ruolo di Observer e di Subject possa essre svolto, in un programma ampio, da diverse sezioni dell'intero insieme, rendendo l'applicazione della struttura Observer particolarmente utile e flessibile.


Visitor


L'ultima struttura che descriviamo e' ancora una struttura relativa al funzionamento di un programma (Behavioral Patterns) e si chiama Visitor. Tale struttura pemette di aggiungere funzionalita' ad una classe senza modoficarne la struttura.

La strategia utilizzata e' schematizzata in figura 29: il Client, cioe' il programma main(), conosce la classe Element e la classe Visitor. Supponiamo ora di voler aggiungere una nuova funzionalita' alla classe Element. Element puo' inoltre essre la classe base da cui ereditano classi concrete ConcreteElement1 e ConcreteElement2. Per implementare la nuova funzionalita', e' sufficiente implementare nella classe Element un metodo accept(Visitor v), che naturalmente, tramite il meccanismo del Polimorfismo, puo' essere diverso in ciascuna delle classi concrete ConcreteElement1 e ConcreteElement2. Il metodo accept(Visitor v) invoca semplicement il metodo visit1() o visit2() di Visitor, a seconda del tipo di elemento concreto ConcreteElement1 e ConcreteElement2. In questo modo alla classe Element e' stata aggiunta la funzionalita' dei metodi visit1() e visit2() di Visitor, cioe' Element puo' "eseguire" questi due metodi.

Inoltre, a seconda del tipo concreto di Visitor, ConcreteVisitor1 e ConcreteVisitor2, il metodo effettivo applicato sara' diverso, grazie al meccanismo del Polimorfismo, e questo aggiunge ulteriore flessibilita' all'intero insieme I metodi visit1() e visit2() hanno inoltre come argomenti gli oggetti concreti ConcreteElement1 e ConcreteElement2, per cui conoscono tutto degli oggetti a cui fanno riferimento, come un metodo applicato direttamente ad Element. Questo realizza proprio l'estensione delle funzionalita' di Element, che e' cio' che avevamo indicato come obiettivo della struttura Visitor.



Applicazioni a problemi di interesse fisico




Un esempio di singleton; i Manager di Geant4


Un esempio di applicazione della struttura Singleton e’ quella della gestione dei Manager in un programma di ricostruzione o di simulazione, cioe’ delle sezioni di programma che si occupano di varie funzioni all’interno dell’intera struttura.

I Manager si utilizzano ad esempio nel programma Geant4, per lo simulazione dell’interazione delle particelle con i materiali. Le applicazioni sono riferite a tutti i casi in cui questo aspetto e’ rilevante: simulazione per la progettazione di apparati di alta energia, di apparati da installare su satellite o per studi di fisica sanitaria.

Un header file per la dichiarazione di una classe di tipo Singleton assume la forma:
#ifndef AnalysisManager_h

#define AnalysisManager_h

#include

#include

#include

#include

#include

#include "AGManager/ArgoAnalysis.h"

#include "AGManager/ArgoAFile.h"
typedef list AA_List ;

typedef AA_List::const_iterator AAL_Iter ;


typedef map > AAF_Map ;

typedef AAF_Map::const_iterator AAFM_Iter ;


class AnalysisManager {

private:


static AnalysisManager * _analysismanager;

AA_List _analysis_list ;

AAF_Map _file_map ;

AnalysisManager() ;


public:

// destructor

virtual ~AnalysisManager() ;
// Static Pointer

static AnalysisManager * GetPointer() ;

…………………………………. // altre parti di codice

};

#endif


L’implementation file contiene l’implementazione del metodo GetPointer(), che e’ cio’ che ci interessa in riferimento all’utilizzo del Singleton:
#include "AnalysisManager.h"
//static self-pointer

AnalysisManager* AnalysisManager:: =0;

AnalysisManager::AnalysisManager() {} ;

AnalysisManager::~AnalysisManager() {} ;

AnalysisManager * AnalysisManager:: {

if (! _analysismanager) {

cout<< "Analysis Manager created" << endl;

_analysismanager = new AnalysisManager;

}

return _analysismanager;



};
Notiamo semplicemente la forma dell’implementazione del metodo GetPointer(), che assicura che venga istanziato un solo oggetto di tipo AnalysisManager e l’inizializzazione, all’inizio del file, della variabile statica privata _analysismanager, in modo da poter effettuare correttamente l’istruzione if all’interno di GetPointer() la prima volta in cui viene utilizzato tale metodo.


La struttura geometrica dell'apparato ARGO-YBJ: composto-composito


In figura 30 e’ mostrato lo sviluppo di uno sciame nell’atmosfera: una particella incidente, interagendo con un nucleo dell’atmosfera, innesca un meccanismo a cascata.

Figura 30

Rappresentazione dello sviluppo di uno sciame nell'atmosfera.

L’apparato ARGO-YBJ, in corso di installazione in Tibet, vicino a Lhasa, e’ stato progettato per rivelare gli sciami indotti dai raggi cosmici o da raggi gamma, fornendo una immagine elettronica dello sciame a livello del rivelatore. In figura 31 e’ mostrato uno schema dell’intero apparato, sul quale sono riportati i segnali dovuti ad un evento simulato.


Figura 31 ( a fianco)

Schema della struttura di insieme del rivelatore ARGO-YBJ




Figura 32 ( sotto)

Schema della struttura modulare (a “scatole cinesi”) delle varie parti che compongono il rivelatore ARGO-YBJ





La struttura dell’apparato e’ modulare, per evidenti ragioni di costruzione e di gestione dell’intero sistema. L’intero apparato e’ suddiviso in cluster, che a loro volta sono suddivisi in camere (RPC) , che contengono le Pad. Sono anche possibili strutture logiche piu’ ampie, i Supercluster, o intermedie, i Moduli, che contengono le camere e sono piu’ piccoli dei cluster. La figura 32 illustra la struttura di insieme del rivelatore.



Per rappresentare la struttura dell’apparato e’ possibile utilizzare un modello “ingenuo”, rappresentato in figura 33, in cui ciascun elemento e’ in relazione di aggregazione con gli elementi che lo costituiscono.

Come gia’ discusso, questo schema non e’ sufficientemente elastico e richiede che parti simili di codice vengano scritte piu’ volte. La soluzione si ottiene utilizzando la struttura Composite, come discusso nel capitolo precedente. La struttura di insieme e’ mostrata, treamite un diagramma UML, in figura 34, dal quale si possono ricavare molti dettagli dell’implementazione. Si noti l’uso dei template e delle relazioni di ereditarieta’ sulle classi finali.

Figura 33: Rappresentazione ingenua delle relazioni di aggregazione tra i vari componenti del rivelatore ARGO-YBJ.






Figura 34: Rappresentazione delle relazioni di aggregazione tra i vari componenti del rivelatore ARGO-YBJ effettuata utilizzando la struttura Composite. Si noti anche l'uso di classi template.







Condividi con i tuoi amici:
1   ...   16   17   18   19   20   21   22   23   24


©astratto.info 2019
invia messaggio

    Pagina principale