Sei sulla pagina 1di 29

6 SYSTEM DESIGN: DECOMPOSING THE SYSTEM

La progettazione del sistema è la trasformazione di un modello di analisi in un modello di progettazione del


sistema. Durante la progettazione del sistema, gli sviluppatori definiscono gli obiettivi di progettazione del
progetto e scompongono il sistema in sottosistemi più piccoli che possono essere realizzati dai singoli
team. Gli sviluppatori selezionano anche strategie per costruire il sistema, come la strategia
hardware/software, la strategia di gestione dei dati persistente, il flusso di controllo globale, la politica di
controllo degli accessi e la gestione delle condizioni limite. Il risultato della progettazione del sistema è un
modello che include una decomposizione del sottosistema e una chiara descrizione di ciascuna di queste
strategie.
La progettazione del sistema non è algoritmica. Gli sviluppatori devono fare compromessi tra molti obiettivi
di progettazione che spesso sono in conflitto tra loro. Inoltre non possono anticipare tutti i problemi di
progettazione che dovranno affrontare perché non hanno ancora un quadro chiaro del dominio della
soluzione. La progettazione del sistema è scomposta in diverse attività, ognuna delle quali affronta parte
del problema generale della decomposizione del sistema:

• Identificare gli obiettivi di progettazione. Gli sviluppatori identificare e dare la priorità alle qualità del
sistema che dovrebbero ottimizzare.
• Progetta la decomposizione iniziale del sottosistema. Gli sviluppatori scompongono il sistema in parti più
piccole in base ai modelli di caso d'uso e di analisi. Gli sviluppatori utilizzano gli stili architettonici standard
come punto di partenza durante questa attività.
• Perfezionare la decomposizione del sottosistema per affrontare gli obiettivi di progettazione. L'iniziale
scomposizione di solito non soddisfa tutti gli obiettivi di progettazione. Gli sviluppatori affinano fino a
quando tutti gli obiettivi sono soddisfatti.

In questo capitolo ci concentriamo sulle prime due attività. Nel prossimo capitolo, raffiniamo la
decomposizione del sistema e forniamo un esempio approfondito con il caso studio di ARENA.

6.1 A FLOOR PLAN EXAMPLE


La progettazione del sistema, la progettazione degli oggetti, e l’implementazione costituisce la costruzione
del sistema. Durante queste tre attività, gli sviluppatori colmano il divario tra le specifiche dei requisiti,
prodotte nell’elaborazione e l’analisi dei requisiti, e il sistema che viene consegnato agli utenti. La
progettazione del sistema è il primo step in questo processo e si focalizza sulla decomposizione del sistema
in parti gestibili. Durante l’elaborazione e l’analisi dei requisiti, ci concentriamo sullo scopo e le funzionalità
del sistema. Durante la progettazione del sistema, ci focalizziamo sui processi, sulle strutture dati, sui
componenti software e hardware necessari per implementarlo. La sfida della progettazione del sistema è
che molti conflitti e vincoli contrastanti devono essere soddisfatti quando si decompone il sistema.
Consideriamo per esempio, il compito di progettare una casa residenziale. Dopo aver concordato col cliente
il numero dei piani e delle stanze, la dimensione della zona giorno, la posizione della casa, l’architetto deve
progettare la planimetria, cioè dove i muri, le porte, e le finestre dovrebbero essere. Lui deve farlo secondo
una serie di requisiti funzionali: la cucina deve essere vicino la sala da pranzo e il garage, il bagno deve
essere vicino alla camera da letto e così via. L’architetto può anche su una serie di norme quando stabilisce
le dimensioni di ogni stanza e la locazione della porta: mobili della cucina sono disponibili in incrementi fissi
e i letti sono disponibili in misure standard. Si noti, comunque, che l’architetto non ha bisogno di sapere
l’esatto contenuto di ogni stanza e le disposizione dei mobili; al contrario queste decisioni dovrebbero
essere lasciate al cliente.
Figure 6-1 mostra tre revisioni successive della planimetria di una casa residenziale.
Ci prefissiamo di soddisfare i seguenti vincoli:
1. Questa casa dovrebbe avere due camere da letto, uno studio, una cucina, e una zona
soggiorno.

2. La distanza complessiva che gli occupanti percorrono ogni giorno dovrebbe essere ridotta al
minimo.

3. L’uso della luce naturale dovrebbe essere massimizzato.


Per soddisfare i vincoli precedentemente descritti, assumiamo che la maggior parte del cammino debba
essere fatto tra la porta d’ingresso e la cucina, quando la spesa viene scaricata dalla macchina, e tra la
cucina e la zona soggiorno, quando i piatti sono trasportati prima e dopo i pasti. Il percorso da
minimizzare è il percorso tra le camere da letto e i bagni. Assumiamo che gli occupanti della casa
spenderanno la maggior parte del loro tempo nella zona soggiorno e nella camera padronale.
Nella prima versione della nostra planimetria, troviamo che la zona soggiorno è troppo lontana dalla
cucina. Per risolvere il problema, la cambiamo con la camera 2. Questo ha anche il vantaggio di
spostare il soggiorno nella parete sud della casa. Nella seconda revisione abbiamo scoperto che la
cucina e le scale sono troppo lontane dall’entrata principale. Per ovviare a questo problema, spostiamo
l’ingresso principale nella parete nord. Questo ci consente di riorientare la camera 2 e spostare il bagno
più vicino ad entrambe le camere da letto. Il soggiorno è incrementato e soddisfiamo i vincoli originali.
A questo punto, possiamo posizionare le porte e le finestre di ogni camera per soddisfare i requisiti
localizzati. Dopo aver fatto questo, abbiamo completato la progettazione senza la conoscenza
dettagliata della disposizione di ogni camera. I piani per l’impianto idraulico, linee elettriche e condotti
di riscaldamento possono procedere.
La progettazione di una planimetria in architettura è simile alla progettazione del sistema in ingegneria
del software. Il tutto è diviso in componenti e interfacce più semplici, tenendo conto dei requisiti
funzionali e non funzionali. La progettazione del sistema incide sulle attività di implementazione e si
traduce in costose rielaborazioni se modificate in seguito. La progettazione delle singole componenti
viene ritardata a dopo.
6.2 AN OVERVIEW OF SYSTEM DESIGN
L’analisi dei risultati nel modello dei requisiti è costituito dai seguenti prodotti:
- Un insieme di requisiti non funzionali e vincoli, tra cui il massimo tempo di risposta, minimo
rendimento, affidabilità, piattaforma del sistema operativo e così via
- Un modello dei casi d’uso, descrive le funzionalità del sistema dal punto di vista dell’attore
- Un modello ad oggetti, descrive le entità manipolate dal sistema
- Un diagramma di sequenza per ogni caso d'uso, che mostra la sequenza di interazioni tra gli oggetti
che partecipano al caso d'uso.

Il modello ad analisi descrive il sistema completamente sotto il punto di vista degli attori e funge da
base di comunicazione tra il cliente e gli sviluppatori. Il modello ad analisi, comunque non contiene
informazioni sulla struttura interna del sistema, la sua configurazione hardware, o più generalmente
come il sistema dovrebbe essere realizzato. La progettazione del sistema è il primo step in questa
direzione. La progettazione del sistema si traduce nei seguenti prodotti:
- Design goals descrive le qualità del sistema che gli sviluppatori dovrebbero ottimizzare
- Software architeture descrive la scomposizione del sottosistema in termini di responsabilità del
sottosistema, dipendenze tra sottosistemi, mappatura del sottosistema sull'hardware e principali
decisioni strategiche quali flusso di controllo, controllo di accesso e memorizzazione dei dati
- Boundary use case descrive la configurazione del sistema, startup, shutdown, problemi di gestione
delle eccezioni

Gli scopi della progettazione derivano dai requisiti non funzionali. Gli obiettivi della progettazione guidano
le decisioni che devono essere prese dagli sviluppatori quando servono compromessi. La decomposizione
del sottosistema costituisce il grosso della progettazione del sistema. Gli sviluppatori dividono il sistema in
pezzi gestibili per affrontare la complessità: ogni sottosistema viene assegnato ad un team e realizzato in
modo indipendente. Perché questo sia possibile, gli sviluppatori devono affrontare problemi di sistema a
livello di scomposizione del sistema. In questo capitolo, descriviamo il concetto di decomposizione del
sottosistema e discutiamo esempi di scomposizioni generiche del sistema chiamate "stili architettonici." Nel
prossimo capitolo, descriviamo come la scomposizione del sistema viene perfezionata per raggiungere
obiettivi specifici di progettazione.
La figura 6-2 mostra la relazione tra la progettazione di sistemi e altre attività di ingegneria del software.
6.3 SYSTEM DESIGN CONCEPTS
In questa sezione, descriviamo più dettagliatamente le decomposizioni dei sottosistemi e le loro
proprietà. In primo luogo, definiamo il concetto di sottosistema e la sua relazione con le classi (sezione
6.3.1). Passiamo ora all'interfaccia dei sottosistemi (sezione 6.3.2): i sottosistemi forniscono servizi ad altri
sottosistemi.
Un servizio è un insieme di operazioni correlate che condividono uno scopo comune. Durante la
progettazione del sistema, definiamo i sottosistemi in termini di servizi che forniscono. Successivamente,
durante la progettazione degli oggetti, definiamo l'interfaccia del sottosistema in termini di operazioni che
fornisce. Esaminiamo poi due proprietà dei sottosistemi, l'accoppiamento e la coesione (sezione 6.3.3).
L'accoppiamento misura le dipendenze tra due sottosistemi, mentre la coesione misura le dipendenze tra
le classi all'interno di un sottosistema. La decomposizione ideale del sottosistema dovrebbe minimizzare
l'accoppiamento e massimizzare la coesione. Poi, guardiamo alla stratificazione e al partizionamento, due
tecniche per relazionare i sottosistemi tra loro (Sezione 6.3.4). La stratificazione consente di organizzare un
sistema come una gerarchia di sottosistemi, ciascuno dei quali fornisce servizi di livello superiore al
sottosistema sovrastante utilizzando servizi di livello inferiore dai sottosistemi sottostanti. Il
partizionamento organizza i sottosistemi come pari che si forniscono reciprocamente servizi diversi. Nella
Sezione 6.3.5, descriviamo una serie di architetture software tipiche che si trovano nella pratica.

6.3.1 SUBSYSTEM AND CLASSES

Nel capitolo 2, Modellazione con UML, abbiamo introdotto la distinzione tra dominio dell'applicazione e
dominio della soluzione. Per ridurre la complessità del dominio dell'applicazione, abbiamo identificato parti
più piccole chiamate "classi" e le abbiamo organizzate in pacchetti. Allo stesso modo, per ridurre la
complessità del dominio delle soluzioni, scomponiamo un sistema in parti più semplici, chiamate
"sottosistemi", che sono composte da un certo numero di classi di domini delle soluzioni. Un sottosistema è
una parte sostituibile del sistema con interfacce ben definite che incapsulano lo stato e il comportamento
delle sue classi contenute. Un sottosistema corrisponde tipicamente alla quantità di lavoro che un singolo
sviluppatore o un singolo team di sviluppo può affrontare. Scomponendo il sistema in sottosistemi
relativamente indipendenti, i team concorrenti possono lavorare su singoli sottosistemi con un minimo
sovraccarico di comunicazione. Nel caso di sottosistemi complessi, applichiamo ricorsivamente questo
principio e scomponiamo un sottosistema in sottosistemi più semplici (vedi Figura 6-3).

Ad esempio, il sistema di gestione degli incidenti descritto in precedenza può essere


scomposto in sottosistema Dispatcherinterface, realizzando l'interfaccia utente per il Dispatcher; un
sottosistema Fieldofficerinterface, realizzando l'interfaccia utente per il Fieldofficer; un sottosistema
Incidentmanagement, responsabile della creazione, la modifica e lo stoccaggio degli incidenti; un
sottosistema Resourcemanagement, responsabile del monitoraggio delle risorse disponibili (ad esempio,
camion e ambulanze); una gestione delle mappe per la rappresentazione di mappe e luoghi; e un
sottosistema di notifica, implementare la comunicazione tra i terminali Fieldofficer e le stazioni Dispatcher.
Questa decomposizione del sottosistema è descritta nella figura 6-4 utilizzando componenti UML.
I componenti sono raffigurati come rettangoli con l'icona del componente nell'angolo in alto a destra.
Le dipendenze fra i componenti possono essere descritte con le frecce tratteggiate del bastone. In UML, i
componenti possono rappresentare sia i componenti logici che fisici. Una componente logica corrisponde a
un sottosistema che non ha alcun equivalente di run-time esplicito, ad esempio, singole componenti
aziendali che sono composte insieme in un unico livello di logica applicativa run-time. Un componente
fisico corrisponde a un sottosistema che, come un equivalente esplicito di run-time, per esempio, un server
di database.

Diversi linguaggi di programmazione (ad esempio Java e Modula-2) forniscono costrutti per sottosistemi di
modellazione (pacchetti in Java, moduli in Modula-2). In altri linguaggi, come C o C++, i sottosistemi non
sono esplicitamente modellati, quindi gli sviluppatori usano le convenzioni per raggruppare le classi (ad
esempio, un sottosistema può essere rappresentato come una directory contenente tutti i file che
implementano il sottosistema). Se i sottosistemi siano o meno esplicitamente rappresentati nel linguaggio
di programmazione, gli sviluppatori devono documentare attentamente la decomposizione del
sottosistema in quanto i sottosistemi sono di solito realizzati da diversi team.

6.3.2 SERVICES AND SUBSYSTEM INTERFACES

Un sottosistema è caratterizzato dai servizi che fornisce ad altri sottosistemi. Un servizio è un insieme di
operazioni correlate che condividono uno scopo comune. Un sottosistema che fornisce un servizio di
notifica, ad esempio, definisce le operazioni per l'invio di avvisi, la ricerca di canali di notifica e la
sottoscrizione e la cancellazione di un canale. L'insieme delle operazioni di un sottosistema a disposizione di
altri sottosistemi costituisce l'interfaccia del sottosistema.
L'interfaccia del sottosistema include il nome delle operazioni, i loro parametri, i loro tipi e i loro valori di
ritorno. La progettazione del sistema si concentra sulla definizione dei servizi forniti da ciascun
sottosistema, ossia sulla enumerazione delle operazioni, dei loro parametri e del loro comportamento di
alto livello. La progettazione degli oggetti si concentrerà sull'interfaccia del programmatore di applicazioni
(API), che perfeziona ed estende le interfacce del sottosistema. L'API include anche il tipo di parametri e il
valore di ritorno di ogni operazione.
Le interfacce fornite e necessarie possono essere raffigurate in UML con connettori di assemblaggio,
chiamati anche connettori a sfera e presa. L'interfaccia fornita viene visualizzata come un'icona a sfera
(chiamata anche lollipop) con il suo nome accanto ad esso. L'interfaccia richiesta viene visualizzata come
icona socket. La dipendenza tra due sottosistemi è mostrata collegando la sfera e l'incavo corrispondenti
nello schema dei componenti.
La figura 6-5 mostra le dipendenze tra i sottosistemi Fieldofficerinterface, Dispatchterinterface e
Resourcemanagement. Il Fieldofficerinterface richiede al Resourceupdateservice di aggiornare lo status e la
posizione del Fieldofficer.
Il Dispatcherinterface richiede al Resourceallocationservice di identificare le risorse disponibili e assegnarle a
nuovi Incidenti.
Il sottosistema Resourcemanagement fornisce entrambi i servizi. Si noti che usiamo la notazione ball-and-
socket quando la decomposizione del sottosistema è già abbastanza stabile e che il nostro obiettivo si è
spostato dall'identificazione dei sottosistemi alla definizione dei servizi. Durante le prime fasi di
progettazione del sistema, potremmo non avere una comprensione così chiara dell'assegnazione delle
funzionalità ai sottosistemi, nel qual caso usiamo la notazione di dipendenza (frecce tratteggiate) della
Figura 6-4.
La definizione di un sottosistema in termini di servizi che fornisce aiuta a concentrarci sulla sua interfaccia
anziché sulla sua attuazione. Quando si scrive un'interfaccia del sottosistema, si dovrebbe cercare di
minimizzare la quantità di informazioni fornite sull'implementazione. Ad esempio, l'interfaccia di un
sottosistema non dovrebbe fare riferimento a strutture di dati interne, come elenchi collegati, array o
tabelle hash. Questo ci permette di minimizzare l'impatto del cambiamento quando revisioniamo
l'implementazione di un sottosistema. Più in generale, vogliamo minimizzare l'impatto del cambiamento
minimizzando le dipendenze tra i sottosistemi.

6.3.3 COUPULING AND COHESION

La copulazione è il numero di dipendenze tra due sottosistemi.

Se due sistemi sono liberamente accoppiati, essi sono relativamente indipendenti, quindi la modifica ad
uno dei sottosistemi avrà un piccolo impatto sull’altro. Se due sottosistemi sono fortemente accoppiati, le
modifiche ad un sottosistema avrà probabilmente un impatto sull’altro. Una proprietà desiderabile di una
decomposizione del sottosistema è che i sottosistemi sono accoppiati il più possibile in modo non corretto.
Questo minimizza l’impatto che gli errori o i cambiamenti futuri in un sottosistema hanno su un altro
sistema.
Consideriamo, per esempio, il sistema di risposta alle emergenze descritto nella figura 6-4. Durante la
progettazione del sistema, decidiamo di allocare tutti i dati persistenti (es: tutti i dati che sopravvivono ad
una singola esecuzione del programma) in un database relazionale. Questo porta ad un sottosistema
aggiuntivo chiamato Database (Figure 6-6). Inizialmente, noi progettiamo l’interfaccia del database e
sottosistema in modo che i sottosistemi che hanno bisogno di memorizzare i dati semplicemente eseguono
i comandi in un linguaggio di interrogazione adatto al database, come SQL.
Ad esempio, il sottosistema Incidentmanagement rilascia query SQL per memorizzare e recuperare i record
che rappresentano Incidents nel database. Questo porta ad una situazione con un elevato accoppiamento
tra il sottosistema Database e i tre sottosistemi client (vale a dire, Incidentmanagement,
Resourcemanagement, e Mapmanagement) che hanno bisogno di memorizzare e recuperare i dati, qualsiasi
cambiamento nel modo in cui i dati sono memorizzati richiederà modifiche nei sottosistemi client. Per
esempio, se cambiamo i fornitori della base di dati dovremo cambiare i sottosistemi per usare un dialetto
differente della lingua di richiesta. Per ridurre l'accoppiamento tra questi quattro sottosistemi, decidiamo di
creare un nuovo sottosistema, chiamato Storage, che protegge il Database dagli altri sottosistemi. In questa
alternativa, i tre sottosistemi client utilizzano i servizi forniti dal sottosistema Storage, che è poi
responsabile per l'emissione di interrogazioni in SQL al sottosistema Database. Se decidiamo di cambiare i
fornitori di database o di utilizzare un meccanismo di archiviazione diverso (ad esempio: file piatti),
abbiamo solo bisogno di cambiare il sottosistema Storage. Di conseguenza, l'accoppiamento complessivo
della decomposizione del sottosistema è stato ridotto. Si noti che la riduzione dell'accoppiamento non è
fine a se stessa. Nell'esempio precedente, la riduzione dell'accoppiamento ha portato a un'ulteriore
complessità. Riducendo l'accoppiamento, gli sviluppatori possono introdurre molti strati inutili di astrazione
che consumano tempo di sviluppo e tempo di elaborazione.
L'accoppiamento elevato è un problema solo se è probabile che un sottosistema cambi.

La coesione è il numero di dipendenze senza un sottosistema. Se un sottosistema contiene molti oggetti


che sono collegati tra loro e svolgono compiti simili, la sua coesione è elevata.
Se un sottosistema contiene un certo numero di oggetti non correlati, la sua coesione è bassa. Una
proprietà desiderabile di una decomposizione del sottosistema è che porta a sottosistemi con alta coesione.
Ad esempio, prendere in considerazione un sistema di tracciamento delle decisioni per registrare problemi
di progettazione, discussioni, valutazioni alternative, decisioni e la loro attuazione in termini di compiti
(Figura 6-7). Designproblem e Option rappresentano l'esplorazione dello spazio di progettazione:
formuliamo il sistema in termini di una serie di Designproblems e documentiamo ogni Option esplorata. La
classe Criterion rappresenta le qualità che ci interessano. Una volta valutate le Option esplorate rispetto ai
Criteri desiderabili, implementiamo le Decisioni in termini di Tasks. I tasks sono ricorsivamente scomposti
in Subtask abbastanza piccoli da essere assegnati a singoli sviluppatori. Noi chiamiamo i task atomici
Actionitems.
Il sistema di tracciamento delle decisioni è abbastanza piccolo da poter raggruppare tutte queste classi in
un sottosistema chiamato DecisionSubsystem (vedi Figura 6-7). Tuttavia, osserviamo che il modello di
classe può essere diviso in due sottografi. Uno, chiamato Rationalesubsystem, contiene le classi
Designproblem, Option, Criterion e Decision. L'altro, chiamato Planningsubsystem, contiene Task, Subtask
e Actionitem (vedi Figura 6-8). Entrambi i sottosistemi hanno una coesione più elevata rispetto al
sottosistema decisionale originale. Questo ci permette di riutilizzare ogni parte in modo indipendente, in
quanto altri sottosistemi hanno bisogno solo della parte di pianificazione o della parte razionale.
Inoltre, i sottosistemi risultanti sono più piccoli del sottosistema originale, permettendoci di assegnare
ciascuno di essi ad un singolo sviluppatore. L'accoppiamento tra i sottosistemi è relativamente basso, con
una sola associazione tra i due sottosistemi. In generale, c'è un compromesso tra coesione e
accoppiamento. Spesso possiamo aumentare la coesione scomponendo il sistema in sottosistemi più
piccoli. Tuttavia, questo aumenta anche l'accoppiamento con l'aumentare del numero di interfacce. Una
buona euristica è che gli sviluppatori possono affrontare 7+-2 concetti a qualsiasi livello di astrazione. Se ci
sono più di nove sottosistemi a un dato livello di astrazione, o se un sottosistema fornisce più di nove
servizi, si dovrebbe considerare di rivedere la decomposizione. Allo stesso modo, il numero di strati non
dovrebbe essere superiore a 7 +-2. Infatti, buona progettazione dei sistemi può essere spesso realizzato con
soli tre strati.

6.3.4 LAYERS AND PARTITIONS

Una decomposizione gerarchica di un sistema produce un insieme ordinato di strati. Un layer è un


raggruppamento di sottosistemi che forniscono servizi correlati, eventualmente realizzati utilizzando servizi
di un altro layer.
I layer (strati) sono ordinati in modo che ogni livello può dipendere solo da livelli di livello inferiore e non ha
alcuna conoscenza dei livelli sopra di esso. Il livello che non dipende da nessun altro livello è chiamato
livello inferiore, e il livello che non viene utilizzato da nessun altro è chiamato livello superiore (Figura 6-9).
In un'architettura chiusa, ogni livello può accedere solo al livello immediatamente sottostante. In
un'architettura aperta, un livello può anche accedere a livelli più profondi.

Un esempio di architettura chiusa è il Modello di Riferimento di Interconnessione dei Sistemi Aperti (in
breve, il modello OSI), che è composto da sette strati [Day & Zimmermann, 1983]. Ogni livello è
responsabile dell'esecuzione di una funzione ben definita. Inoltre, ogni livello fornisce i propri servizi
utilizzando i servizi del livello sottostante (Figura 6-10). Il livello fisico rappresenta l'interfaccia hardware
alla rete. È responsabile della trasmissione di bit su un canale di comunicazione. Il livello Datalink è
responsabile della trasmissione di frame di dati senza errori utilizzando i servizi del livello fisico. Il livello di
rete è responsabile della trasmissione e dell'instradamento dei pacchetti all'interno di una rete. Il livello di
trasporto è responsabile di garantire che i dati siano trasmessi in modo affidabile da un punto all'altro. Il
livello Transport è l'interfaccia che i programmatori Unix vedono quando trasmettono informazioni su
socket TCP/IP tra due processi. Il livello di sessione è responsabile dell'inizializzazione e dell'autenticazione
di una connessione. Il livello Presentazione esegue servizi di trasformazione dati, come lo swap di byte e la
crittografia. Il livello di applicazione è il sistema che si sta progettando (a meno che non si sta costruendo
un sistema operativo o uno stack di protocollo). Il livello di applicazione può anche consistere di
sottosistemi stratificati.
Fino a poco tempo fa, solo i quattro strati inferiori del modello OSI erano ben standardizzati. Unix e molti
sistemi operativi desktop, ad esempio, forniscono interfacce a TCP/IP che implementano i livelli Transport,
Network e Datalink. Lo sviluppatore dell'applicazione doveva ancora colmare il divario tra il livello
Transport e il livello Application. Con il crescente numero di applicazioni distribuite, questa lacuna ha
motivato lo sviluppo di middleware come CORBA [OMG, 2008] e Java RMI [RMI, 2009]. CORBA e Java RMI ci
consentono di accedere in modo trasparente agli oggetti remoti inviando loro messaggi mentre i messaggi
vengono inviati ad oggetti locali, implementare efficacemente i livelli Presentazione e Sessione (vedi Figura
6-11).

Un esempio di architettura aperta è il toolkit di interfaccia utente Swing per Java [JFC, 2009]. Il livello più
basso è fornito dal sistema operativo o da un sistema di finestre, come X11, e fornisce la gestione delle
finestre di base. AWT è un'interfaccia finestra astratta fornita da Java per schermare le applicazioni da
piattaforme di finestre specifiche. Swing è una libreria di oggetti interfaccia utente che fornisce una vasta
gamma di servizi, dai pulsanti alla gestione della geometria. Un'applicazione di solito accede solo
all'interfaccia Swing. Tuttavia, il livello Applicazione può bypassare il livello Swing e accedere direttamente
a AWT. In generale, l'apertura dell'architettura permette agli sviluppatori di bypassare i livelli più alti per
risolvere i colli di bottiglia delle prestazioni (Figura 6-12).

Le architetture a strati chiuse hanno proprietà desiderabili: portano a un basso accoppiamento tra
sottosistemi, e i sottosistemi possono essere integrati e testati in modo incrementale. Ogni livello, tuttavia,
introduce una velocità e lo stoccaggio overhead che può rendere difficile soddisfare i requisiti non
funzionali. Inoltre, l'aggiunta di funzionalità al sistema in revisioni successive può rivelarsi difficile,
soprattutto quando le aggiunte non erano previste. In pratica, un sistema viene raramente scomposto in
più di tre o cinque strati.

Un altro approccio per affrontare la complessità è quello di suddividere il sistema in sottosistemi peer,
ognuno responsabile di una diversa classe di servizi. Ad esempio, un sistema di bordo per una macchina
potrebbe essere scomposto in un servizio di viaggio che fornisce indicazioni in tempo reale per il
conducente, un servizio di preferenze individuali che ricorda la posizione del sedile del conducente e
stazione radio preferita, e il servizio del veicolo che traccia il consumo di benzina della vettura, le
riparazioni, e la manutenzione programmata. Ogni sottosistema dipende liberamente dagli altri, ma spesso
può funzionare in isolamento.

In generale, la decomposizione di un sottosistema è il risultato sia del partizionamento che della


stratificazione. Partizioniamo prima il sistema in sottosistemi di alto livello, che sono responsabili di
funzionalità specifiche o eseguire su uno specifico nodo hardware. Ognuno dei sottosistemi che ne
derivano, se la complessità lo giustifica, viene scomposto in livelli di livello inferiore e inferiore fino a
quando non sono abbastanza semplici da essere implementati da un singolo sviluppatore. Ogni
sottosistema aggiunge un certo overhead di elaborazione a causa della sua interfaccia con altri
sistemi. L'eccessivo partizionamento o stratificazione può aumentare la complessità.

6.3.5 ARCHITECTURAL STYLES

Con l'aumentare della complessità dei sistemi, la specifica della decomposizione del sistema è critica. È
difficile modificare o correggere la decomposizione debole una volta che lo sviluppo è iniziato, come la
maggior parte delle interfacce sottosistema dovrebbe cambiare. Riconoscendo l'importanza di questo
problema, è emerso il concetto di architettura del software. Un'architettura software include la
scomposizione del sistema, il flusso di controllo globale, la gestione delle condizioni di confine e i protocolli
di comunicazione intersubsystem [Shaw & Garlan, 1996]. In questa sezione, descriviamo diversi stili
architettonici che possono essere utilizzati come base per l'architettura di diversi sistemi. Questa non è
affatto un'esposizione sistematica o completa del soggetto. Vogliamo piuttosto fornire alcuni esempi
rappresentativi e rinviare il lettore alla letteratura per maggiori dettagli.
Repository

Nello stile architettonico del repository (vedi Figura 6-13), i sottosistemi accedono e modificano una
singola struttura di dati chiamata repository centrale. I sottosistemi sono relativamente indipendenti e
interagiscono solo attraverso il repository. Il flusso di controllo può essere dettato dal repository centrale
(es: trigger sui sistemi periferici di invocazione dati) o dai sottosistemi (es: flusso indipendente di controllo e
sincronizzazione attraverso le serrature nel repository).

I repository sono tipicamente utilizzati per i sistemi di gestione dei database, come un sistema di libro paga
o un sistema bancario. La posizione centrale dei dati facilita la gestione dei problemi di concorrenza e
integrità tra sottosistemi. Compilatori e ambienti di sviluppo software seguono anche uno stile
architettonico repository (Figura 6-14). I diversi sottosistemi di un compilatore accedono e aggiornano un
albero di analisi centrale e una tabella dei simboli. I debugger e gli editor di sintassi accedono anche alla
tabella dei simboli.

Il sottosistema repository può essere utilizzato anche per l'attuazione del flusso di controllo
globale. Nell'esempio del compilatore della Figura 6-14, ogni singolo strumento (ad esempio, il compilatore,
il debugger e l'editor) viene invocato dall'utente. Il repository assicura solo che gli accessi simultanei siano
serializzati. Al contrario, il repository può essere utilizzato per richiamare i sottosistemi in base allo stato
della struttura dati centrale. Questi sistemi sono chiamati "sistemi di lavagna." Il sistema di comprensione
vocale HEARSAY II [Erman et al., 1980], uno dei primi sistemi di lavagna, invocava strumenti basati sullo
stato attuale della lavagna.

I repository sono adatti per applicazioni con attività di elaborazione dati complesse e in continua
evoluzione. Una volta che un repository centrale è ben definito, possiamo facilmente aggiungere nuovi
servizi sotto forma di sottosistemi aggiuntivi. Lo svantaggio principale dei sistemi di repository è che il
repository centrale può diventare rapidamente un collo di bottiglia, sia dal punto di vista delle prestazioni
che da quello della modificabilità. L'accoppiamento tra ogni sottosistema e il repository è elevato,
rendendo così difficile cambiare il repository senza avere un impatto su tutti i sottosistemi.
Model/View/Controller

Nello stile architettonico Model/View/Controller (MVC) (Figura 6-15), i sottosistemi sono classificati in tre
diversi tipi: i sottosistemi modello mantengono la conoscenza del dominio, i sottosistemi vista lo
visualizzano all'utente e i sottosistemi controller gestiscono la sequenza di interazioni con l'utente. I
sottosistemi modello sono sviluppati in modo tale da non dipendere da alcun sottosistema di controllo o di
visualizzazione. Le modifiche del loro stato sono propagate al sottosistema vista tramite un protocollo di
sottoscrizione/notifica. Il MVC è un caso speciale del repository in cui Model implementa la struttura dati
centrale e gli oggetti di controllo dettano il flusso di controllo.

Ad esempio, le figure 6-16 e 6-17 illustrano la sequenza di eventi che si verificano in uno stile architettonico
MVC. Figura 6-16 visualizza due visualizzazioni di un file system. La finestra inferiore elenca il contenuto
della cartella Software Engineering basata su Comp, incluso il file 9DesignPatterns2.ppt. La finestra in alto
mostra le informazioni su questo file. Il nome del file 9DesignPatterns2.ppt appare in tre posizioni: in
entrambe le finestre e nel titolo della finestra superiore. Supponiamo ora di cambiare il nome del file in
9DesignPatterns.ppt. La Figura 6-17 mostra la sequenza di eventi:

1. L'Infoview e il Folderview sottoscrivono entrambe le modifiche ai modelli di file visualizzati (quando


vengono creati).
2. L'utente digita il nuovo nome del file.
3. Il controllore, l'oggetto responsabile dell'interazione con l'utente durante le modifiche del nome del file,
invia una richiesta al modello.
4. Il modello modifica il nome del file e notifica la modifica a tutti gli abbonati.
5. Sia Infoview che Folderview vengono aggiornati, in modo che l'utente veda un cambiamento coerente.

La funzionalità di sottoscrizione e notifica associata a questa sequenza di eventi è di solito realizzata con un
modello di design Observer (vedi Sezione A.7). Il modello Observer permette agli oggetti Model e View di
essere ulteriormente disaccoppiati rimuovendo le dipendenze dirette dal modello alla vista. Per maggiori
dettagli, il lettore si riferisce a [Gamma et al., 1994] e alla Sezione A.7.
La logica tra la separazione di Model, View e Controller è che le interfacce utente, cioè la View e il
Controller, sono molto più spesso soggette a modifiche rispetto alla conoscenza del dominio, cioè il
Model. Inoltre, eliminando qualsiasi dipendenza dal Modello sulla Vista con il protocollo di
sottoscrizione/notifica, le modifiche delle viste (interfacce utente) non hanno alcun effetto sui sottosistemi
del modello. Nell'esempio della Figura 6-16, potremmo aggiungere una vista shell in stile Unix del file
system senza dover modificare il file system. Abbiamo descritto una scomposizione simile nel Capitolo 5,
Analisi, quando abbiamo identificato entità, confine e oggetti di controllo. Questa decomposizione è anche
motivata dalle stesse considerazioni sul cambiamento.
MVC è adatto per sistemi interattivi, soprattutto quando sono necessarie più visualizzazioni dello stesso
modello. MVC può essere utilizzato per mantenere la coerenza tra i dati distribuiti; tuttavia introduce la
stessa strozzatura delle prestazioni come per altri stili di repository.
Client/server

Nello stile architettonico client/server (Figura 6-18), un sottosistema, il server, fornisce servizi a istanze di
altri sottosistemi chiamati client, che sono responsabili dell'interazione con l'utente. La richiesta di un
servizio è di solito effettuata tramite un meccanismo di chiamata a procedura remota o un broker di oggetti
comune (ad esempio, CORBA, Java RMI, o HTTP). Il flusso di controllo nei client e nei server è indipendente
tranne che per la sincronizzazione per gestire le richieste o per ricevere i risultati.

Un sistema informativo con un database centrale è un esempio di client/server


stile architettonico. I clienti sono responsabili per la ricezione di input da parte dell'utente, l'esecuzione di
controlli di autonomia, e avviare le transazioni di database quando tutti i dati necessari vengono raccolti. Il
server è quindi responsabile dell'esecuzione della transazione e della garanzia dell'integrità dei dati. In
questo caso, uno stile architettonico client/server è un caso speciale dello stile architettonico del repository
in cui la struttura dati centrale è gestita da un processo. I sistemi client/server, tuttavia, non sono limitati a
un singolo server. Sul World Wide Web, un singolo client può facilmente accedere ai dati da migliaia di
server diversi (Figura 6-19).
Gli stili di architettura client/server sono adatti per sistemi distribuiti che gestiscono grandi quantità di dati.

Peer-to-peer
Uno stile architettonico peer-to-peer (vedi Figura 6-20) è una generalizzazione dello stile architettonico
client/ server in cui i sottosistemi possono agire sia come client che come server, nel senso che ogni
sottosistema può richiedere e fornire servizi. Il flusso di controllo all'interno di ciascun sottosistema è
indipendente dagli altri tranne che per le sincronizzazioni su richiesta.
Un esempio di stile architettonico peer-to-peer è un database che accetta richieste dall'applicazione e
notifica all'applicazione ogni volta che alcuni dati vengono modificati (Figura 6-21).
I sistemi peer-to-peer sono più difficili da progettare rispetto ai sistemi client/server perché introducono la
possibilità di blocchi e complicano il flusso di controllo.

I callback sono operazioni temporanee e personalizzate per uno scopo specifico. Per esempio, un Dbuser
peer in Figura 6-21 può dire al DBMS peer quale operazione invocare dopo una notifica di modifica. Il
Dbuser allora usa l'operazione di callback specificata da ogni Dbuser per la notifica quando un
cambiamento accade. I sistemi peer-to-peer in cui un peer "server" invoca i peer "client" solo attraverso i
callback sono spesso indicati come sistemi client/server, anche se questo è impreciso dal momento che il
"server" può anche avviare il flusso di controllo.

Three-Thier
Lo stile architettonico a tre livelli organizza i sottosistemi in tre strati (Figura 6-22):
• Il livello di interfaccia include tutti gli oggetti di confine che si occupano dell'utente, comprese le finestre,
i moduli, le pagine web e così via.
• Il livello logico dell'applicazione include tutti gli oggetti di controllo e entità, realizzando l'elaborazione, il
controllo delle regole e la notifica richiesta dall'applicazione.
• Lo storage layer realizza l'archiviazione, il recupero e la query di oggetti persistenti.
Lo stile architettonico a tre livelli è stato inizialmente descritto negli anni '70 per i sistemi di
informazione. Lo storage layer, un'analogia al sottosistema Repository nello stile architettonico del
repository, può essere condiviso da diverse applicazioni che operano sugli stessi dati. A sua volta, la
separazione tra il livello dell'interfaccia e il livello logico dell'applicazione consente lo sviluppo o la modifica
di diverse interfacce utente per la stessa logica applicativa.

Four-tier
Lo stile architettonico a quattro livelli è un'architettura a tre livelli in cui il livello Interface viene scomposto
in un livello Presentation Client e un livello Presentation Server (Figura 6-23). Il livello del client di
presentazione si trova sulle macchine utente, mentre il livello del server di presentazione può essere
posizionato su uno o più server. L'architettura a quattro livelli consente un'ampia gamma di client di
presentazione nell'applicazione, riutilizzando alcuni degli oggetti di presentazione tra i clienti. Ad esempio,
un sistema di informazioni bancarie può includere una serie di clienti diversi, come ad esempio
un'interfaccia browser Web per gli utenti domestici, una macchina automatica Teller, e un client di
applicazione per i dipendenti della banca. I moduli condivisi da tutti e tre i client possono quindi essere
definiti ed elaborati nel layer Presentation Server, eliminando così la ridondanza tra i client.

Pipe and filter


Nello stile architettonico del tubo e del filtro (Figura 6-24), i sottosistemi elaborano i dati ricevuti da un
insieme di input e inviano i risultati ad altri sottosistemi tramite un insieme di output.
I sottosistemi sono chiamati “filtri”, e le associazioni tra i sottosistemi sono chiamate “pipe”. Ogni filtro
conosce solo il contenuto e il formato dei dati ricevuti sui tubi di ingresso, non i filtri che li hanno prodotti.
Ogni filtro viene eseguito contemporaneamente, e la sincronizzazione viene effettuata tramite i tubi. Lo
stile architettonico del tubo e del filtro è modificabile: i filtri possono essere sostituiti con altri o
riconfigurati per raggiungere uno scopo diverso.
L'esempio più noto di stile architettonico di pipe e filtri è la shell Unix [Ritchie & Thompson, 1974]. La
maggior parte dei filtri sono scritti in modo tale che leggono il loro input e scrivono i loro risultati su tubi
standard. Questo permette ad un utente Unix di combinarli in molti modi diversi.
La figura 6-25 mostra un esempio di quattro filtri. L'output di ps (stato del processo) viene immesso in grep
(ricerca di un modello) per rimuovere tutti i processi che non sono di proprietà di un utente
specifico. L'output di grep (cioè, i processi di proprietà dell'utente) viene poi ordinato per ordinamento e
inviato a più, che è un filtro che visualizza il suo ingresso in un terminale, uno schermo alla volta.
Gli stili di pipe e filtri sono adatti per sistemi che applicano trasformazioni a flussi di dati senza l'intervento
degli utenti. Non sono adatti per sistemi che richiedono interazioni più complesse tra componenti, come un
sistema di gestione delle informazioni o un sistema interattivo.
6.4 SYSTEM DESIGN ACTIVITIES: FROM OBJECT TO SUBSYSTEM
La progettazione del sistema consiste nel trasformare il modello di analisi nel modello di progettazione che
tiene conto dei requisiti non funzionali descritti nel documento di analisi dei requisiti. Illustriamo queste
attività con un esempio, Mytrip, un sistema di pianificazione del percorso per gli automobilisti. Iniziamo con
il modello di analisi di Mytrip; poi descriviamo l'identificazione degli obiettivi di progettazione (Sezione
6.4.2) e la progettazione di una decomposizione iniziale del sistema (Sezione 6.4.3).

6.4.1 Starting Point: Analysis Model for a Route Planning System

Utilizzando Mytrip, un conducente può pianificare un viaggio da un computer di casa contattando un


servizio di pianificazione del viaggio sul Web (Plantrip in Figura 6-26). Il viaggio viene salvato per un
successivo recupero sul server. Il servizio di pianificazione del viaggio deve supportare più di un
conducente.

L'autista poi va alla macchina e inizia il viaggio, mentre il computer di bordo dà indicazioni in base alle
informazioni di viaggio dal servizio di pianificazione e la sua posizione attuale indicata da un sistema GPS di
bordo (Executetrip in Figura 6-27).
Eseguiamo l'analisi per il sistema Mytrip seguendo le tecniche descritte nel capitolo 5, Analisi, e ottenere il
modello in figura 6-28.
6.4.2 IDEMTIFYING DESIGN GOALS

La definizione degli obiettivi di progettazione è il primo passo della progettazione del sistema. Identifica le
qualità su cui il nostro sistema dovrebbe concentrarsi. Molti obiettivi di progettazione possono essere
dedotti dai requisiti non funzionali o dal dominio dell'applicazione. Altri dovranno essere sollecitati dal
client. È tuttavia necessario dichiararle esplicitamente in modo che ogni importante decisione di
progettazione possa essere presa coerentemente seguendo lo stesso insieme di criteri.
Ad esempio, alla luce dei requisiti non funzionali per Mytrip descritti nella Sezione 6.4.1, identifichiamo
l'affidabilità e la tolleranza di guasto alla perdita di connettività come obiettivi di
progettazione. Identifichiamo quindi la sicurezza come un obiettivo di progettazione, in quanto numerosi
driver avranno accesso allo stesso server di pianificazione del viaggio. Aggiungiamo la modificabilità come
obiettivo di progettazione, in quanto vogliamo fornire la possibilità per i conducenti di selezionare un
servizio di pianificazione del viaggio di loro scelta. Il riquadro seguente riassume gli obiettivi di
progettazione che abbiamo identificato.

In generale, possiamo selezionare gli obiettivi di progettazione da una lunga lista di qualità altamente
desiderabili. Le tabelle da 6-2 a 6-6 elencano una serie di possibili criteri di progettazione. Questi criteri
sono organizzati in cinque gruppi: prestazioni, affidabilità, costo, manutenzione e criteri per l'utente
finale. Le prestazioni, l'affidabilità e i criteri dell'utente finale sono solitamente specificati nei requisiti o
dedotti dal dominio dell'applicazione. I criteri di costo e manutenzione sono dettati dal cliente e dal
fornitore.
I criteri di prestazione (tabella 6-2) comprendono i requisiti di velocità e di spazio imposti al sistema. Il
sistema dovrebbe essere reattivo, o dovrebbe realizzare un numero massimo di compiti? Lo spazio di
memoria è disponibile per le ottimizzazioni della velocità, o la memoria dovrebbe essere usata con
parsimonia?

I criteri di affidabilità (tabella 6-3) determinano l'entità dello sforzo da compiere per minimizzare gli
incidenti di sistema e le loro conseguenze. Con quale frequenza il sistema può bloccarsi? Quanto a
disposizione dell'utente dovrebbe essere il sistema? Il sistema dovrebbe tollerare errori e guasti? I rischi
per la sicurezza sono associati all'ambiente del sistema? I problemi di sicurezza sono associati ai crash del
sistema?

I criteri di costo (tabella 6-4) comprendono il costo per sviluppare il sistema, per utilizzarlo e amministrarlo.
Si noti che i criteri di costo non includono solo considerazioni di progettazione, ma anche quelle gestionali.
Quando il sistema sostituisce uno più vecchio, si deve tener conto del costo della retrocompatibilità o del
passaggio al nuovo sistema. Ci sono anche compromessi tra diversi tipi di costi, come i costi di sviluppo, i
costi di formazione degli utenti finali, i costi di transizione e i costi di manutenzione. Mantenere la
retrocompatibilità con un sistema precedente può aumentare i costi di sviluppo riducendo i costi di
transizione.

I criteri di manutenzione (tabella 6-5) determinano quanto sia difficile cambiare il sistema dopo
l'implementazione. Quanto facilmente si possono aggiungere nuove funzionalità? Quanto facilmente le
funzioni esistenti possono essere riviste? Il sistema può essere adattato a un diverso dominio
applicativo? Quanto sforzo sarà necessario per portare il sistema su una piattaforma diversa? Questi criteri
sono più difficili da ottimizzare e pianificare, in quanto raramente è chiaro il successo del progetto e per
quanto tempo il sistema sarà operativo.
I criteri per l'utente finale (tabella 6-6) comprendono qualità auspicabili dal punto di vista dell'utente, ma
non ancora coperte dai criteri di prestazione e affidabilità. Il software è difficile da usare e da imparare? Gli
utenti possono eseguire le attività necessarie sul sistema?
Spesso questi criteri non ricevono molta attenzione, soprattutto quando il cliente che contrae il sistema è
diverso dai suoi utenti.

Quando si definiscono gli obiettivi di progettazione, solo un piccolo sottoinsieme di questi criteri può essere
preso in considerazione contemporaneamente. È, per esempio, irrealistico sviluppare software che sia
sicuro, sicuro ed economico. In genere, gli sviluppatori devono dare la priorità agli obiettivi di progettazione
e scambiarli uno contro l'altro, nonché contro gli obiettivi manageriali come il progetto corre in ritardo o
oltre il budget. La tabella 6-7 elenca diversi possibili compromessi.
Gli obiettivi manageriali possono essere scambiati con obiettivi tecnici (ad esempio, tempi di consegna vs.
funzionalità). Una volta che abbiamo una chiara idea degli obiettivi di progettazione, possiamo procedere a
progettare una decomposizione iniziale del sottosistema.

6.4.3 IDENTIFYING SUBSYSTEMS

Trovare sottosistemi durante la progettazione del sistema è simile a trovare oggetti durante l'analisi. Ad
esempio, alcune delle tecniche di identificazione degli oggetti che abbiamo descritto nel Capitolo 5, Analisi,
come l'euristica di Abbotts, sono applicabili all'identificazione del sottosistema. Inoltre, la decomposizione
dei sottosistemi viene costantemente rivista ogni volta che vengono affrontate nuove questioni: diversi
sottosistemi sono fusi in un unico sottosistema, un sottosistema complesso è suddiviso in parti e alcuni
sottosistemi vengono aggiunti per affrontare nuove funzionalità. Le prime iterazioni sulla decomposizione
del sottosistema possono introdurre drastici cambiamenti nel modello di progettazione del sistema. Questi
sono spesso meglio gestiti attraverso il brainstorming.

La decomposizione iniziale del sottosistema deve essere derivata dai requisiti funzionali. Ad esempio, nel
sistema Mytrip, identifichiamo due grandi gruppi di oggetti: quelli coinvolti nel caso di utilizzo di Plantrip e
quelli coinvolti nel caso di utilizzo di Executetrip. Le classi Viaggio, Direzione, Attraversamento, Segmento e
Destinazione sono condivise tra entrambi i casi d'uso. Questo insieme di classi è strettamente accoppiato in
quanto viene utilizzato nel suo complesso per rappresentare un viaggio.
Decidiamo di assegnarli con Planningservice al Planningsubsystem, e il resto delle classi sono assegnate al
Routingsubsystem (Figura 6-29). Questo porta ad una sola associazione che attraversa i confini del
sottosistema. Si noti che questa decomposizione del sottosistema è un repository in cui il sistema
Planningsubsystem è responsabile della struttura centrale dei dati.

Un'altra euristica per l'identificazione del sottosistema è quella di tenere insieme oggetti funzionalmente
correlati. Un punto di partenza consiste nell'assegnare ai sottosistemi gli oggetti partecipanti che sono stati
identificati in ogni caso d'uso. Alcuni gruppi di oggetti, come il gruppo Trip a Mytrip, sono condivisi e
utilizzati per comunicare informazioni da un sottosistema all'altro. Possiamo creare un nuovo sottosistema
per ospitarli o assegnarli al sottosistema che crea questi oggetti.

Incapsulamento sottosistemi con il modello di progettazione di facciata


La decomposizione del sottosistema riduce la complessità del dominio della soluzione minimizzando
l'accoppiamento tra sottosistemi. Il modello Facade (vedi Appendice A.6 e [Gamma et al., 1994]) ci
permette di ridurre ulteriormente le dipendenze tra le classi incapsulando un sottosistema con
un'interfaccia semplice e unificata. Ad esempio, in Figura 6-30, la classe Compiler è una facciata che
nasconde le classi Codegenerator, Optimizer, Parsenode, Parser e Lexer. La facciata consente l'accesso solo
ai servizi pubblici offerti dal sottosistema e nasconde tutti gli altri dettagli, riducendo di fatto
l'accoppiamento tra sottosistemi.
I sottosistemi identificati durante la decomposizione iniziale del sottosistema spesso derivano dal
raggruppamento di diverse classi funzionalmente correlate. Questi sottosistemi sono buoni candidati per il
modello di progettazione della facciata e dovrebbero essere incapsulati in una classe.

6.5 FURTHER READINGS


Storicamente, la disciplina dell'architettura software ha avuto origine con Dijkstra e Parnas. Hanno sottolineato che la
struttura di un pezzo di software è critica quanto la sua capacità di calcolare i risultati corretti. Dijkstra introdusse il
concetto di architettura a strati e ne discusse l'applicazione alla progettazione del sistema operativo T.H.E. [Dijkstra,
1968]. Parnas ha introdotto il concetto di nascondere le informazioni e discusso i criteri che dovrebbero essere
utilizzati quando si decompone un sistema [Parnas, 1972]. Il metodo Structured Design [Yourdon & Constantine, 1979]
ha introdotto i concetti di coesione e metriche di accoppiamento per progettare un'architettura software.
Sebbene la necessità e i vantaggi per le architetture software siano stati ben compresi da allora, il campo
dell'architettura software ha continuato ad evolversi per diversi decenni. Le barriere principali finora sono state la
mancanza di un linguaggio comune per descrivere le architetture e la mancanza di metodi di analisi per confrontare le
architetture e prevedere se possono soddisfare i requisiti specificati del sistema. Software Architecture: Perspectives
on an Emerging Discipline [Shaw & Garlan, 1996] e Pattern-Oriented Software Architecture [Buschmann et al., 1996]
sono i primi sforzi sistematici ampiamente citati per fornire un catalogo di architetture. Shaw e Garlan introdussero il
concetto di stili architettonici; Buschmann et al. utilizzarono modelli architettonici come linguaggio di descrizione.
Negli anni '90, un maggior numero di casi studio nel l'ingegneria delle linee di prodotto hanno fornito esempi concreti
dei vantaggi del riutilizzo delle architetture software, non solo da un punto di vista tecnico (le decisioni di
progettazione in materia di struttura e componenti sono riutilizzate), ma anche da un punto di vista gestionale (le
organizzazioni del gruppo e i metodi di comunicazione sono riutilizzati). Il Software Engineering Institute della Carnegie
Mellon University ha raccolto, mantenuto e sviluppato ampio materiale sull'architettura del software e sulle linee di
prodotto. Ad esempio, Software Architecture in Practice [Bass et al., 2003] e Evaluating Software Architectures
[Clements et al., 2002] descrivono metodi e casi di studio per la selezione e la valutazione dell'architettura software.
Rilevante anche per l'architettura software è l'architettura software applicata [Hofmeister, 2000], che si concentra
sulle applicazioni industriali pratiche dell'architettura software.

Potrebbero piacerti anche