L'obiettivo del paradigma orientato ad oggetti è quello di incapsulare i dettagli dell'implementazione. Perciò
in relazione alla persistenza si desidera ottenere un oggetto permanente che assomigli a quello transitorio. Non
si deve necessariamente essere a conoscenza del fatto che l'oggetto sia permanente, o gestirlo in modo diverso da
qualsiasi altro oggetto. Almeno questo è l'obiettivo.
In pratica ci potranno essere delle volte in cui l'applicazione debba controllare diversi aspetti della persistenza:
-
quando gli oggetti permanenti vengono letti o scritti
-
quando gli oggetti permanenti vengono eliminati
-
come vengono gestite le transazioni
-
come si raggiunge il controllo del blocco e della simultaneità
Vi sono due case da considerare: la prima volta che l'oggetto viene scritto nell'archivio persistente, e le volte
successive in cui l'applicazione deve aggiornare l'archivio dell'oggetto permanente con modifiche all'oggetto.
In entrambi i casi, il meccanismo specifico dipende dalle operazioni supportate dal framework di persistenza. In
generale, il meccanismo utilizzato è quello di inviare un messaggio al framework di persistenza per creare un oggetto
permanente. Una volta che l'oggetto è permanente, il framework di persistenza è abbastanza "intelligente" da rilevare
le modifiche successive e scriverle nell'archivio dell'oggetto permanente quando necessario (solitamente quando è in
corso una transazione).
Un esempio di creazione di un oggetto permanente è riportato di seguito:
L'oggetto PersistenceMgr è un'istanza di VBOS, un framework di persistenza. OrderCoordinator crea un ordine persistente
inviandolo come argomento di un messaggio 'createPersistentObject' a PersistenceMgr.
È generalmente non necessario modellare ciò in maniera esplicita, a meno che non sia importante sapere che
l'oggetto sia stato archiviato in modo esplicito in un determinato punto di una sequenza di eventi. Se delle operazioni
successive dovranno effettuare una query sull'oggetto, esso dovrà essere presente nel database, e quindi sarà
importante sapere che sarà presente in quel punto.
È necessario recuperare l'oggetto dall'archivio degli oggetti permanenti prima che l'applicazione possa inviargli un
messaggio. Ricordarsi che il lavoro in un sistema orientato agli oggetti viene eseguito inviando messaggi agli oggetti.
Ma se l'oggetto a cui di vuole inviare un messaggio è nel database e non ancora in memoria, allora c'è un problema: non
si può inviare un messaggio a qualcosa che non esiste ancora!
In breve, si deve inviare un messaggio ad un oggetto che sa come effettuare una query ad un database, recuperare
l'oggetto corretto, ed istanziarlo. Quindi, e solo allora, sarà possibile inviare il messaggio originario come inteso.
L'oggetto che istanzia l'oggetto permanente viene a volte definito oggetto factory. Un oggetto factory è
responsabile della creazione di istanze di oggetti, compresi quelli permanenti. Data una query, l'oggetto
factory potrà essere progettato per restituire una serie di uno o più oggetti che corrispondono ad essa.
Generalmente gli oggetti sono molto collegati tra loro tramite le associazioni, quindi sarà solitamente necessario
recuperare unicamente l'oggetto principale di un grafico di oggetti; i rimanenti verranno 'tirati' fuori in modo
trasparente dal database tramite le loro associazioni con l'oggetto principale. (Un buon meccanismo di persistenza
prevede ciò: recupera gli oggetti solo quando sono necessari; altrimenti, si potrebbero istanziare un ampio numero di
oggetti inutilmente. Il recupero di oggetti prima dell'effettiva necessità è uno dei principali di problemi di
prestazioni causato da meccanismi di persistenza semplicistici.)
L'esempio che segue illustra come modellare il recupero di oggetti da un archivio di oggetti permanenti. In un
effettivo diagramma sequenza, il DBMS non verrebbe mostrato, siccome dovrebbe essere incapsulato in un oggetto
factory.
Il problema con gli oggetti permanenti è che sono permanenti! A differenza degli oggetti transitori che scompaiono con
il processo che li ha creati, quelli permanenti continuano ad esistere fin quando non vengono esplicitamente eliminati.
È quindi importante cancellare l'oggetto quando non lo si utilizza più.
Il problema è che ciò è difficile da determinare. Il fatto che un'applicazione abbia finito di utilizzare un oggetto
non significa che altre applicazioni, presenti e future, non debbano utilizzarlo. E siccome gli oggetti possono ed
hanno associazioni di cui essi stessi non sono a conoscenza, non è sempre facile capire se è possibile cancellarli.
In progettazione questo potrà essere rappresentato semanticamente utilizzando diagrammi di stato: quando un
oggetto raggiunge lo stato fine, potrà essere rilasciato. Gli sviluppatori responsabili
dell'implementazione di classi permanenti potranno utilizzare le informazioni del diagramma di stato per richiedere al
comportamento del meccanismo di persistenza adeguato di rilasciare l'oggetto. La responsabilità del progettista della
realizzazione di caso d'uso è quella di richiamare le operazioni appropriate che causino il raggiungimento dello stato
fine da parte dell'oggetto quando è giusto che questo venga eliminato.
Se un oggetto è ampiamente connesso ad altri oggetti, potrebbe essere difficile determinare se potrà essere eliminato.
Poiché gli oggetti factory sono a conoscenza della struttura dell'oggetto quanto l'oggetto a cui sono connessi,
è spesso utile caricare l'oggetto factory con una classe che abbia la responsabilità di determinare se una particolare
istanza può essere eliminata. Il framework della persistenza potrà anch'esso fornire supporto per questa capacità.
Le transazioni definiscono una serie di invocazioni di operazioni che sono atomiche; o sono tutte eseguite o
nessuna lo è. Nel contesto della persistenza, una transazione definisce una serie di modifiche su una serie di oggetti
che saranno eseguite tutte o nessuna. Le transazioni consentono congruenza, assicurando che le serie di oggetti si
spostino da una stato coerente ad un altro.
Vi sono diverse opzioni per mostrare le transazioni nelle realizzazioni del caso d'uso:
-
Testo. Utilizzando degli script a margine di diagrammi sequenza, i confini della transazione potranno essere
documentati come mostrato di seguito. Questo metodo è semplice e consente di utilizzare un qualsiasi numero di
meccanismi per implementare la transazione.
Rappresentazione dei confini della transazione utilizzando annotazioni di testo.
-
Utilizzando messaggi espliciti. Se il meccanismo di gestione della transazione utilizzato fa uso di messaggi
espliciti per avviare e terminare una transazione, questi potranno essere mostrati esplicitamente nel diagramma
sequenza, come riportato di seguito:
Un diagramma sequenza che mostra messaggi espliciti per avviare e terminare una transazione.
Gestione delle condizioni di errore
Se tutte le operazioni specificate in una transazione non potranno essere eseguite (solitamente perché si è verificato
un errore), la transazione viene interrotta, e tutte le modifiche effettuate vengono annullate. Le condizioni di
errore previste spesso rappresentano flussi di eventi eccezionali nel caso d'uso. In altri casi, le condizioni di
errore si verificano a causa di un errore nel sistema. Le condizioni di errore devono essere documentate nelle
interazioni. Gli errori semplici e le eccezioni potranno essere mostrate nelle interazioni in cui si verificano; gli
errori complessi e le eccezioni potrebbero necessitare di interazioni proprie.
Le modalità di errore di oggetti specifici potranno essere visualizzate in diagrammi di stato. La gestione del flusso
di controllo condizionato di queste modalità di errore potrà essere visualizzata nell'interazione in cui l'errore o
l'eccezione si verificano.
La simultaneità descrive il controllo dell'accesso a risorse di sistema critiche nel corso di una transazione. Per
poter mantenere il sistema in uno stato congruente, una transazione potrebbe richiedere che esso abbia accesso
esclusivo a determinate risorse chiave. L'esclusività potrebbe includere la capacità di leggere e/o scrivere in una
serie di oggetti.
Di seguito viene riportato un semplice esempio del perché potrebbe essere necessario restringere l'accesso ad una serie
oggetti. Si supponga di lavorare su semplice sistema di immissione di ordini. Le persone chiamano per immettere gli
ordini, questi vengono elaborati ed inviati. L'ordine potrebbe essere considerato come una specie di transazione.
Per illustrare la necessità di un controllo della simultaneità, supponiamo che arrivi un ordine per un nuovo paio di
scarponi da escursione. Quando l'ordine viene immesso nel sistema, questo controlla se gli scarponi della misura
richiesta sono in magazzino. Se vi sono, verranno riservati, così che nessuno li possa comprare prima dell'invio
dell'ordine. Una volta inviato l'ordine, gli scarponi vengono rimossi dal magazzino.
Nel periodo che intercorre tra l'immissione dell'ordine ed il suo invio, gli scarponi saranno in uno stato particolare,
saranno in magazzino, ma "dedicati" all'ordine immesso. Se l'ordine venisse annullato per una qualsiasi ragione, gli
scarponi verranno riportati in magazzino. Una volta inviato l'ordine, si assume che la società non voglia mantenere un
record che indichi che prima c'erano gli scarponi.
L'obiettivo della simultaneità, come quello delle transazioni, è di assicurare che il sistema si sposti da uno stato
congruente ad un altro. Inoltre la simultaneità si sforza di assicurare che la transazione abbia tutte le risorse
necessarie per completare il lavoro. Il controllo della simultaneità può essere implementato in molti modi diversi,
compreso il blocco delle risorse, semafori, blocco della memoria condivisa e spazi di lavoro privati.
In un sistema orientato ad oggetti, è difficile dire, basandosi sui soli pattern di messaggio, se un determinato
messaggio possa causare il cambiamento di stato di un oggetto. Inoltre, implementazioni differenti potranno ovviare
alla necessità di restringere l'accesso a determinati tipi di risorse; ad esempio, alcune implementazioni forniscono a
ciascuna transazione la propria vista dello stato del sistema al loro inizio. In questo caso, altri processi potranno
modificare lo stato di un oggetto senza influenzare la 'vista' di qualsiasi altra transazione in esecuzione.
Per evitare di limitare l'implementazione, nella progettazione si indicheranno semplicemente le risorse a cui la
transazione deve avere accesso esclusivo. Utilizzando l'esempio precedente, si vuole indicare che si necessita di
accesso esclusivo agli scarponi ordinati. Un'alternativa semplice è quella di annotare una descrizione del messaggio
inviato, indicando che l'applicazione necessità di controllo esclusivo sull'oggetto. L'implementatore utilizzerà questa
informazione per determinare il miglior modo per implementare i requisiti di simultaneità. Un diagramma sequenza di
esempio che mostra le annotazioni di quali messaggi richiedono accesso esclusivo è riportato di seguito. Si assume che
tutti i blocchi vengono rilasciati quando la transazione è completata.
Esempio che mostra il controllo accessi annotato in un diagramma sequenza.
Il motivo per cui non si restringe l'accesso a tutti gli oggetti necessari in una transazione è che spesso solo pochi
oggetti devono avere delle restrizioni di accesso; limitare l'accesso a tutti gli oggetti partecipanti in una
transazione fa perdere risorse valide e può creare, piuttosto che prevenire, dei colli di bottiglia delle
prestazioni.
|