|
Dr. Carlo Pescio Design: Dominio del Problema e della Soluzione |
Pubblicato su Computer Programming No. 41
Completiamo il design del dominio del problema, raffinando i
dettagli della struttura statica e definendo la struttura dinamica del
modello
Introduzione
Nel numero di settembre, prima della digressione della precedente puntata,
abbiamo identificato le componenti essenziali di un modello OO come segue:
Nel presente articolo ci occuperemo del dominio del problema, nell'ottica
del design, e discuteremo gli obiettivi della definizione della struttura
statica e dinamica del modello, nonché alcuni principi di architettura
dei sistemi.
Struttura statica
Il dominio del problema è stato in gran parte identificato durante il
processo di analisi, che ha evidenziato le classi rilevanti ai fini del
modello, le relazioni tra esse esistenti, gli scambi dei messaggi, e così
via. Perché dunque riconsiderarlo a proposito del design? Ricordiamo che
attraverso l'analisi abbiamo stabilito cosa debba fare il sistema;
è ora importante definire, all'interno del dominio del problema, come
tali compiti verranno svolti. Ciò comporterà, come vedremo, l'estensione
o la parziale revisione delle gerarchie di classi coinvolte, in quanto
dovremo necessariamente introdurre nuovi elementi appartenenti al dominio
della soluzione (o più esattamente, di una possibile soluzione tra
le molteplici scelte possibili).
Passare da una descrizione del problema ad una definizione della soluzione,
nell'ambito di un modello object oriented, comporta i seguenti passi:
Vediamo ora nell'ordine i vari passi, iniziando dalla definizione degli algoritmi. Indipendentemente dal paradigma utilizzato, la soluzione di un problema comporta sempre la ricerca e l'utilizzo di un appropriato processo di calcolo o di manipolazione dell'informazione, ovvero un opportuno algoritmo. La formulazione concreta dell'algoritmo può essere diversa, in funzione del paradigma ed anche delle potenzialità del linguaggio target, ma una formulazione astratta dell'algoritmo stesso prescinde in gran parte dal formalismo utilizzato: quicksort resta comunque quicksort, sia esso realizzato attraverso una funzione che opera su array, da un predicato in programmazione logica, o con l'ausilio di una gerarchia di classi aventi una funzione virtuale per il calcolo del minore tra due elementi.
La conoscenza degli algoritmi e delle principali strutture dati (alberi, grafi, hash table, e così via) deve far parte del bagaglio culturale di ogni programmatore o progettista: naturalmente, conoscere una formulazione nell'ambito del paradigma object oriented può essere più vantaggioso, ma è spesso sufficiente una buona conoscenza anche di una formulazione "classica", basata su abstract data types o su una visione funzionale. Scegliere un algoritmo può essere banale in alcuni casi, mentre può diventare un vero e proprio progetto di ricerca, con lo studio e la definizione di nuovi procedimenti, qualora le esigenze siano particolarmente ardue da soddisfare; in ogni caso, avere a disposizione alcuni testi di base sugli algoritmi, come [1], [2] o [3] è una esigenza fondamentale per chiunque sia impegnato in progetti non banali.
Ricordiamo che la scelta di un algoritmo dipende sia da ragioni tecnologiche
che dalla singola applicazione. Le "ragioni tecnologiche" includono
ad esempio l'architettura hardware a disposizione: per citare un esempio
molto banale, spesso occorre decidere se un dato vada memorizzato o ricalcolato
ogni volta; il giusto trade-off non può (in generale) essere scelto in
modo ottimale indipendentemente dalla piattaforma target. D'altra parte,
è l'applicazione stessa che determina la frequenza delle singole operazioni:
ciò che potrebbe essere conveniente con una determinata distribuzione della
frequenza delle operazioni, può non esserlo con un'altra. Un albero perfettamente
bilanciato può avere senso se le operazioni di ricerca sono molto più frequenti
di quelle di inserimento/cancellazione, ma non viceversa.
Quando definiamo o comunque decidiamo l'utilizzo di un determinato algoritmo,
facciamo spesso riferimento a strutture dati (ovvero, nel nostro contesto
di OOD, a classi) ben note e presenti in quasi ogni programma: liste, alberi,
tabelle, matrici, eccetera. Queste classi "base", che infatti
sono spesso contenute nelle librerie di base dei linguaggi, formano le
cosiddette classi di infrastruttura; notiamo che in alcuni casi
sarà necessario arrivare comunque al design dettagliato di classi di infrastruttura,
o fare riferimento ad una particolare implementazione: se la cancellazione
di elementi random di una lista è un'operazione frequente, un'implementazione
della lista con doppi puntatori sarà più indicata di una con singoli puntatori.
In molti casi, tuttavia, possiamo introdurre nel dominio della soluzione
le necessarie classi di infrastruttura senza preoccuparci eccessivamente
dell'implementazione, ma definendo semplicemente opportuni vincoli che
verranno poi passati al programmatore. Ad esempio, potremmo richiedere
che la cancellazione di un elemento avvenga in un tempo costante, o per
essere più precisi abbia complessità computazionale O(1 ).
Oltre a queste classi "generiche", la nostra particolare soluzione potrà richiedere anche la definizione di nuove classi, non facenti parti a priori del dominio del problema, e che sono specializzate per la soluzione identificata. Riprendendo per un istante gli esempi visti nella precedente puntata, la classe "integratore" nell'approccio relativo non appartiene ovviamente all'infrastruttura (è infatti molto specializzata) e neppure al dominio del problema.
Notiamo che talvolta le classi specializzate sono ottenute partizionando
elementi presenti nella visione più astratta fornita dall'analisi: un esempio
è stato dato, sempre nella scorsa puntata, nell'approccio basato su advise-sink,
dove sono state introdotte classi ausiliarie per rappresentare i dati associati
ad una modifica dello stato, che in fase di analisi era semplicemente rappresentata
come uno scambio di messaggi.
Tra le classi specializzate, un ruolo particolare è spesso riconosciuto
alle cosiddette classi di controllo, che interfacciano l'applicazione
con il mondo esterno, inteso sia come sistemi di I/O che come interfaccia
utente. Come già accennato, nel paradigma model/view/controller, i "dati"
dell'applicazione e l'interfaccia utente non sono direttamente collegati,
ma l'interazione passa attraverso classi intermedie (controller); questo
approccio ha il vantaggio di diminuire l'accoppiamento con l'interfaccia
utente, al prezzo di una codifica più complessa. Per questa ragione, molti
framework commerciali si basano su un paradigma model/view (con controller
fissato o embedded), piuttosto che sul model/view/controller.
Specificare le classi
Tutte le classi di cui sopra verranno aggiunte al nostro modello object oriented, arricchendo di conseguenza il risultato dell'analisi; il design non si ferma naturalmente qui, in quanto è ora necessario scendere nel dettaglio di ogni singola classe e definire gli elementi lasciati in sospeso durante la fase di analisi o i passi precedenti.
In particolare, il risultato finale del design deve comprendere:
Vale la pena di insistere sull'importanza di specificare i metodi che
non alterano lo stato dell'oggetto (normalmente detti const dalla
keyword utilizzata in C++) ed i metodi virtuali. Si tratta infatti di una
informazione estremamente importante dal punto di vista della completezza
del modello; notiamo che in alcuni linguaggi tutti i metodi sono
virtuali1 ed è quindi importante
specificare le assunzioni fatte al momento del design, in quanto potrebbe
essere necessario ricorrere ad un renaming in fase di codifica.
Infine, ricordiamo che il livello di dettaglio a cui potete arrivare
in fase di design è spesso arbitrario: in molti casi, una definizione dell'interfaccia
pubblica e una visione ad alto livello degli algoritmi sarà più che sufficiente,
salvo i casi più complessi in cui è necessario definire in modo più preciso
le strutture dati coinvolte e formulare un algoritmo con maggiore precisione.
Come sempre, è difficile dire "quando fermarsi", poiché dipende
molto dall'organizzazione: se chi esegue il design è anche incaricato di
implementare il codice relativo, può trovare utile lasciare il design dettagliato
ad un livello piuttosto astratto, e passare all'implementazione. D'altra
parte, se il design e l'implementazione vengono eseguiti in tempi e/o da
persone differenti, non di rado è necessario fornire maggiori informazioni
in fase di progettazione, onde evitare errate interpretazioni. Non è insolito
che, nel caso del singolo sviluppatore incaricato di un progetto, il design
e l'implementazione si fondano in un processo unico, in cui è difficile
identificare dove termini uno e cominci l'altro; ciò non costituisce in
sé un problema, ammesso che esistano comunque dei "documenti di design"
cui fare riferimento, e non si debba invece estrarre e ricostruire tutta
la conoscenza dal codice. Molte scelte, non solo di livello architetturale,
ma anche riferite alle singole decisioni a livello di strutture utilizzate,
sono difficili o impossibili da ricostruire dal codice: non è quindi raro
che decisioni iniziali, mai documentate, vengano implicitamente mantenute
in future versioni, anche a fronte di modifiche dei requisiti, semplicemente
perché nessuno ne ricorda le motivazioni. Se le frequenze delle operazioni
cambiano, ad esempio, potrebbe avere senso riconsiderare le strutture dati
coinvolte: non di rado, tutttavia, non esiste traccia di come esse siano
state scelte nella prima implementazione. Se nel vostro processo di sviluppo
vi è poco spazio per il design, cercate almeno di motivare le scelte eseguite
come guida per chi dovrà comprendere e modificare in seguito il vostro
codice.
Stratificazione
Prima di proseguire con la descrizione della struttura dinamica del
modello, vorrei riportare l'attenzione su un fattore architetturale molto
importante, che non ha trovato un giusto spazio nelle puntate precedenti.
Si tratta infatti di un criterio semplice ma fondamentale per la corretta
strutturazione di un sistema.
Una delle caratteristiche di un buon design è infatti quella di separare elementi posti a diversi livelli di astrazione; questa caratteristica viene comunemente indicata come stratificazione, intendendo così che ogni sottosistema può essere visto come un insieme di strati, di livello sempre più astratto, ognuno dei quali utilizza, almeno in linea di principio, solo i servizi dello strato sottostante.
Un esempio ben noto di architettura stratificata, indicata in questo caso anche come "a cipolla", è quella di molti sistemi operativi: al più basso livello di astrazione abbiamo l'hardware, al più alto le applicazioni, ed in mezzo diversi strati che isolano dai dettagli dei livelli sottostanti: gestione dei task, della memoria, del file system, e così via.
La stratificazione mira a ridurre le dipendenze tra gli elementi del
sistema, e quindi il fenomeno delle modifiche a cascata; notiamo la differenza
tra stratificazione ed incapsulazione: in un sistema object oriented, l'incapsulazione
nasconde i dettagli implementativi della classe, mentre la stratificazione
riduce gli accoppiamenti sui messaggi. L'incapsulazione in sé non è sufficiente
a proteggere dalle modifiche a cascata, poiché modifiche all'interfaccia
di una classe (che possono comunque avvenire) richiedono necessariamente
un adattamento nelle classi client. L'interposizione di diversi livelli
di astrazione dovrebbe, se ben strutturata, smorzare la propagazione delle
modifiche verso i livelli successivi.
Come si applica il concetto di stratificazione a livello di design object oriented? In gran parte, osservando le connessioni di messaggio, a livello delle categorie di classi, ed all'interno di queste al livello dei singoli oggetti. Da tali connessioni possiamo determinare un grafo diretto, dove due nodi (classi o categorie di classi) sono connessi se e solo se esiste una connessione di messaggio tra un oggetto di una classe (o categoria di classi) e l'altra. Possiamo ora definire un sistema come stratificato se nel grafo delle connessioni associato è possibile assegnare un livello ad ogni classe, tale che tutte le classi di livello n siano connesse solo con classi di livello n-1. In realtà, raggiungere un tale livello di stratificazione è in genere arduo, a meno di non introdurre artificiosamente delle classi cuscinetto. In genere, è più che accettabile se le classi di livello n sono connesse solo a classi di livello inferiore, ma non necessariamente n-1.
Notiamo che questa visione della stratificazione ha il pregio di essere
semi-formale, ma ha il difetto di non attribuire alcun significato agli
strati: una classe "scheduler", una classe "lista"
ed una classe "persona" potrebbero essere poste all'interno dello
stesso strato. Tuttavia, una visione più completa, ma difficilmente formalizzabile,
imporrebbe di avere all'interno dello stesso strato solo classi in qualche
modo omogenee dal punto di vista concettuale. Solo in tal modo l'architettura
del sistema porterà i benefici di maneggevolezza concettuale e di futura
espandibilità e mantenibilità che vogliamo perseguire.
Va detto che non è semplice ottenere un modello stratificato di un sistema
complesso, neppure riferendosi alla versione più debole di stratificazione;
in alcuni casi, la complessità del sistema supera ogni tentativo di imbrigliarla
in una struttura gerarchica. Considerando ad esempio le varianti architetturali
viste nella puntata precedente, un sistema che richieda l'uso degli advise
sink sarà spesso non-stratificato, con connessioni complesse e dinamiche.
Sarà anche più complesso da comprendere nella sua interezza e da modificare,
ma anche le migliori tecniche di ingegneria del software non possono ridurre
la complessità dei sistemi al di sotto dei limiti fisiologici propri dei
sistemi stessi. Tuttavia, quando introducete nuove classi in un sistema,
come parte della attività di design, dovreste sempre cercare di identificarne
lo "strato" di appartenenza e di verificare che, nei limiti del
possibile, il design stesso non stia esplodendo verso una connettività
combinatoria dei diversi elementi.
Struttura dinamica
Una volta definite le interfacce delle classi ad un adeguato livello
di dettaglio, il che può includere la definizione delle strutture dati
e degli algoritmi fondamentali che dovranno svolgere i compiti relativi,
abbiamo terminato il progetto della parte statica del sistema. Ovviamente
vi saranno in seguito dei raffinamenti, in quanto il processo di sviluppo
è per natura iterativo, ed è spesso necessario riconsiderare alcune scelte
mentre il sistema evolve.
D'altra parte, la definizione statica del sistema non è sufficiente
a caratterizzare il comportamento del sistema stesso: essa si concentra
troppo sulla singola classe, con un'enfasi particolare sulla visione esterna
della classe stessa, e non pone nel giusto rilievo le interazioni tra le
classi e gli eventuali vincoli temporali che esse comportano. Inoltre,
talvolta un oggetto può trovarsi in diversi stati ben determinati,
che influenzano in modo molto forte il suo comportamento. In genere, è
difficile comprendere le transizioni di stato da una descrizione statica
del sistema, anche se con un adeguato livello di dettaglio è spesso possibile.
Si ricorre pertanto a descrizioni più espressive, sia per le interazioni
tra oggetti che per la rappresentazione degli stati di un oggetto: queste
fanno parte del dominio della cosiddetta struttura dinamica del
sistema, che tuttavia non si limita a questo. Una parte della struttura
dinamica è rappresentata dalla gestione dei task, che vedremo in una puntata
futura.
Diagramma degli stati
Ad ogni istante, un oggetto si trova in uno stato ben determinato, definito dal valore delle sue variabili di istanza. In alcuni casi, il comportamento dell'oggetto dipende pesantemente dallo stato in cui si trova, o da alcune delle sue componenti. Ad esempio, un telefono si comporta in modo molto diverso se la cornetta è alzata o abbassata; per contro, alcune variabili di istanza influenzano il risultato delle operazioni, ma non il comportamento: il colore influenza il risultato di un metodo Plot() di una classe Linea, ma non ne influenza il comportamento, che è sempre quello di tracciare una linea. Notiamo che la distinzione tra risultato e comportamento può essere molto sottile in alcuni casi, e dipende strettamente da come vengono definiti l'uno e l'altro. Nel contesto dell'articolo, lasceremo la distinzione ad un livello piuttosto intuitivo ed informale; chi desideri maggiori dettagli, può consultare [6].
Quando un oggetto si comporta in modo molto diverso in funzione del
suo stato, lo stato diventa un elemento molto rilevante per comprendere
il funzionamento dell'oggetto, ed è in genere importante documentare in
modo preciso come avvengono le transizioni di stato, e quali conseguenze
tali transizioni abbiano sull'oggetto stesso e sul mondo esterno.
In tal caso si utilizza in genere un diagramma degli stati, ovvero un grafo diretto dove i nodi rappresentano gli stati e gli archi le possibili transizioni tra gli stati. Gli archi sono in genere etichettati per rappresentare lo stimolo che scatena la transizione (proveniente in genere dal mondo esterno, anche se talvolta ha senso considerare eventi interni al sistema).
Un semplice esempio di diagramma degli stati è dato in figura 1, dove
viene rappresentata una versione molto semplificata di un lettore di compact
disc, con tre stati significativi (vuoto, disco inserito, play), e le relative
transizioni. Notiamo che ad ogni stato è associato un nome ed eventualmente
i valori o i range che alcune variabili di istanza devono assumere. È sempre
bene verificare che gli stati siano (un sottoinsieme di) una precondizione
di una operazione, e che siano una postcondizione di un'altra; uno stato
può anche essere definito attraverso un'espressione invariante che lo caratterizza.
Un buon esercizio potrebbe essere il completamento del modello, per
tenere conto di funzionalità più estese del lettore, oppure (ad esempio)
la definizione di un diagramma degli stati di una macchinetta distributrice
di bevande: i modelli più sofisticati, con tanto di display e tastierino,
hanno un diagramma abbastanza complesso da essere interessante ma comunque
sufficientemente semplice da costituire un breve esercizio.
Vale la pena di insistere sul fatto che un diagramma degli stati modella un oggetto e le sue transizioni; gli altri oggetti non sono presenti nel diagramma, se non implicitamente come sorgente degli stimoli. Vedremo tra breve come rappresentare invece le interazioni tra oggetti.
In ogni caso, di norma non modelleremo tutti gli oggetti attraverso
un diagramma degli stati; solo i componenti che interagiscono con l'esterno,
o nei quali è possibile evidenziare stati significativi che risultino in
un diverso comportamento dell'oggetto nei confronti del sistema, richiedono
una specifica di questo tipo. Tornando all'esempio dell'oggetto Linea,
in genere non avremo necessità di definire un diagramma degli stati; anche
se l'oggetto avesse uno stato visibile/non_visibile che influisce sul suo
comportamento, di norma una specifica informale o in pseudocodice sarà
sufficiente per i casi elementari. Il diagramma deve aiutare a comprendere
il funzionamento del sistema, sia in fase di progettazione e discussione
del design che in fase di implementazione o manutenzione. Non deve invece
diventare un lavoro aggiuntivo del progettista nei casi banali.
Quando un oggetto ha comportamenti significativamente diversi in funzione
dei suoi stati, può avere senso implementare l'oggetto stesso come una
macchina a stati, o automa finito. Chi desidera approfondire questo argomento
può consultare un buon testo di informatica generale, oppure qualche riferimento
classico, come [7]. Esistono, nel paradigma object oriented, alternative
interessanti alle macchine a stati: ad esempio, una possibilità è quella
di far coincidere, a livello di implementazione, ogni stato con una classe
derivata da una virtual base class, ed utilizzare un membro di tipo puntatore
all'interno della classe che puo' assumere i diversi stati. In questo modo,
modificando il puntatore modifichiamo di fatto il comportamento della classe;
questo schema è ancora piu' adatto ai linguaggi basati su delegation, ma
è facilmente implementabile anche nei linguaggi più comuni, attraverso
un semplice forwarding delle funzioni. Una simile implementazione è di
fatto un ottimo esempio di raffinamento del dominio del problema, attraverso
l'introduzione di classi accessorie che hanno il solo fine di ridurre la
complessità concettuale del design e dell'implementazione.
Diagramma degli eventi
Il diagramma degli stati è utilizzato per specificare le transizione interne di un singolo oggetto, in risposta agli stimoli provenienti dal mondo esterno o da altri oggetti. Non rivela, tuttavia, le interazioni a livello di sistema, il parallelismo e la concorrenza del sistema stesso, né i vincoli temporali (sia come successione che come temporizzazioni).
Tali interazioni sono indubbiamente una delle parti più complesse da
comprendere in assenza di una adeguata documentazione in fase di design:
la difficoltà maggiore nella comprensione di programmi object oriented
è infatti quasi sempre dovuta ad interazioni non ovvie tra gli oggetti
presenti nel sistema [8]. La presenza del parallelismo rende un diagramma
degli eventi indispensabile per una adeguata comprensione del sistema.
In un diagramma degli eventi (vedere fig. 2), il tempo è rappresentato sull'asse verticale, ed ogni oggetto coinvolto nello scenario in esame viene indicato con una linea verticale. Lo stato dell'oggetto ad ogni istante (attivo o bloccato) viene rappresentato dallo spessore della linea associata, o trasformando la linea in un rettangolo quando l'oggetto è attivo. Linee orizzontali sono associate agli eventi del sistema, e corrispondono normalmente a transizioni da attivo a bloccato (o viceversa) per alcuni degli oggetti coinvolti. Questa non è una condizione necessaria: esistono sistemi in cui tutti gli oggetti sono sempre attivi; si tratta comunque di un'esigua minoranza.
Nell'esempio di figura 2, possiamo vedere un sistema (molto semplificato) dove un driver legge dati da una periferica, li passa ad un oggetto che esegue alcune analisi e trasformazioni su di essi, il quale a sua volta li passa ad un ulteriore oggetto responsabile per la memorizzazione e visualizzazione. Possiamo vedere che è possiblie indicare con semplicità ed immediatezza di lettura alcuni vincoli di temporizzazione, ad esempio che l'intero processo di analisi, visualizzazione e memorizzazione richieda al massimo 3 millisecondi.
Nell'esempio dato, le linee orizzontali (eventi) sono sostanzialmente
chiamate di metodi da parte degli oggetti; in altri casi potrebbero essere
eventi provenienti dal mondo esterno, nel caso non sia stato modellato
come un oggetto esso stesso. Un oggetto può generare più eventi allo stesso
istante, ma ricordate che non ha molto senso, sia dal punto di vista probabilistico
che implementativo, considerare la ricezione simultanea di più eventi.
Simili problemi di sincronizzazione vanno sempre risolti utilizzando degli
appositi oggetti, come semafori, mailbox, eccetera, il cui scopo è appunto
di fornire primitive di sincronizzazione all'intero sistema.
Naturalmente, così come non è normalmente richiesto di rappresentare ogni possibile transizione di stato in un diagramma degli stati, non rappresenteremo ogni possibile scenario di interazione in un diagramma degli eventi: solo gli scenari più significativi per il sistema verranno definiti e documentati graficamente. Ovviamente, la definizione di "significativo" è volutamente ambigua: nel progetto di un distributore di bevande potremo limitarci a definire le interazioni "lecite", lasciando le altre non specificate (non a caso, i distributori di bevande hanno spesso combinazioni non documentate, e piuttosto casuali), mentre in un sistema di controllo di un impianto chimico, ma anche in un bancomat, sarà spesso necessario documentare adeguatamente ogni possibile interazione.
Spesso, utilizziamo un diverso diagramma degli eventi per ogni "scenario"
di alto livello; un oggetto può partecipare (in modo diverso) a più scenari,
nel qual caso possiamo avere più diagrammi. Attenzione in questi casi a
rendere sempre espliciti gli eventuali problemi di concorrenza e competizione
per le risorse.
In progetti con requisiti di correttezza e verificabilità più stringenti, normalmente non ci si può limitare ad uno o più diagrammi degli eventi, ma è necessario dare una specifica formale delle interazioni concorrenti interne al sistema.
In tal caso, si utilizzano spesso dei linguaggi per la specifica di
sistemi concorrenti, come il CSP (Communicating Sequential Processes, vedi
[9]) o il CCS (Calculus of Communicating Systems); esistono anche delle
estensioni orientate agli oggetti di tali linguaggi, così come tentativi
autonomi di definire formalismi analoghi nel paradigma object oriented,
che tuttavia rimangono, al momento, prevalentemente a livello di ricerca
[10].
Conclusioni
Nella prossima puntata focalizzaremo il discorso sulla gestione dei dati, dando una panoramica dei diversi metodi per la persistenza degli oggetti, ma anche recuperando quanto di meglio esiste nell'approccio relazionale.
Alcuni lettori mi hanno scritto chiedendo di trattare degli esempi completi,
dalla specifica informale all'analisi, al design architetturale e di dettaglio;
purtroppo, mentre da un lato potrebbe essere molto interessante, dall'altro
il tempo e lo spazio richiesto sarebbero ben al di là delle possibilità
di una serie di articoli. Posso comunque suggerire un paio di testi, facilmente
reperibili, orientati agli esempi: in [11], l'autore sviluppa un progetto
piuttosto semplice dalla fase di analisi sino al design dettagliato, pur
con poca enfasi sulla progettazione architetturale, usando il metodo di
Booch; in pratica, l'intero libro è sviluppato intorno all'esemplificazione
di un caso. In [12], l'autore propone diversi esempi di progettazione,
usando tuttavia un approccio inusuale: anziché utilizzare sin dalle prime
fasi un approccio object oriented, utilizza metodi misti di data flow analysis
e functional decomposition, recuperando quindi strumenti più datati, e
ponendo di conseguenza l'accento su aspetti molto diversi da quelli considerati
in questa rubrica (ad esempio, viene data grande enfasi alle dipendenze
temporali sin dalla fase di analisi). Potrebbe quindi essere indicato per
chi voglia passare al paradigma object oriented, proveniendo ad esempio
da un ambiente data flow, in modo più graduale ed incrementale, ovviamente
con le dovute cautele.
1ad esempio Smalltalk. Notiamo che anche
se alcuni confondono la programmazione OO "pura" con Smalltalk,
l'assenza di metodi non-virtuali non solo non riflette il comportamento
di alcuni oggetti nel mondo reale, ma impedisce anche di prevenire alcuni
problemi, discussi in un altro articolo su questo stesso numero.
Bibliografia
[1] D. E. Knuth: "The art of computer programming", Vol 1,
2, 3, Addison-Wesley, 1969-1981.
[2] Niklaus Wirth, "Algoritmi + Strutture Dati = Programmi",
Tecniche Nuove, 1987.
[3] Aho, Hopcroft, Ullman: "Data Structures and Algorithms",
1983.
[4] H. Jarvinen: "Object-oriented modeling of reactive systems",
Proceedings ACM Conference on Software Engineering, 1990.
[5] J. Rumbaugh: "Object-Oriented Modeling and Design", Prentice
Hall, 1991.
[6] Ebert, Engels: "Observable or Invocable Behaviour - You Have
to Choose", Technical Reports, Leiden University, 1994.
[7] Hopcroft, Ullman: "Introduction to Automata Theory, Languages
and Computation", Addison-Wesley, 1979.
[8] Wilde, Chapman, Matthews, Huitt: "Describing Object Oriented
Software: What Maintainers Need to Know", Technical Report, University
of West Florida, 1992.
[9] C. A. R. Hoare: "Communicating Sequential Processes",
Prentice Hall, 1985.
[10] O. Nierstrasz: "Toward an Object Calculus", Proceedings
ECOOP'91 Workshop on Object-Based Concurrent Computing, Springer-Verlag.
[11] Iseult White: "Using the Booch Method: a Rational Approach",
Benjamin/Cummings, 1994.
[12] Darrel Ince: "Programmazione ad oggetti in C++, Metodi di
Ingegneria del Software", McGraw-Hill, 1992.
Biografia
Carlo Pescio svolge attività di consulenza in ambito internazionale
sulle metodologie di sviluppo Object Oriented e di programmazione
in ambiente Windows. È laureato in Scienze dell'Informazione,
membro dell'Institute of Electrical and Electronics Engineers
Computer Society, dei Comitati Tecnici IEEE su Linguaggi Formali,
Software Engineering e Medicina Computazionale, dell'ACM e dell'Academy
of Sciences di New York. Può essere contattato tramite
la redazione o su Internet come
pescio@programmers.net