Gli strumenti di programmazione finora mostrati sono solo una piccola parte della strumentazione necessaria alla progettazione e realizzazione di software attinenti applicazioni reali.
Di norma una applicazione richiede migliaia o centinaia di migliaia di linee di codice per cui è di fondamentale importanza avere adeguati strumenti sia di programmazione che di verifica del software.
I grandi progetti software richiedono un gruppo di lavoro e non un singolo individuo e di conseguenza si hanno interazioni continue tra diversi soggetti.
Di qui la necessità di conformare l’attività del gruppo a degli standard:
Anche la qualità del software deve soddisfare ad alcune proprietà:
E’ necessario, quindi, introdurre una metodologia per lo sviluppo del software che abbia due caratteristiche fondamentali:
Nell’ambito dello sviluppo software si identifica il così detto “ciclo di vita del software” che può essere suddiviso in una serie di passi distinti secondo lo schema seguente:
A parte la manutenzione, che deve necessariamente essere effettuata dopo che il software è andato in uso, si hanno due modelli per la progettazione del software:
Waterfall (a cascata) ed Evolutionary (a spirale o evolutivo).
Modello Waterfall: è un modello nel quale ogni passo di progettazione può essere compiuto soltanto dopo il completamento di quello precedente; l’integrazione tra le varie fasi avviene soltanto alla fine dello sviluppo per cui il software è utilizzabile solo alla fine del processo.
Modello Evolutionary: è un modello nel quale si identificano brevi cicli di sviluppo completo con una forte integrazione tra i passi; è spesso presente un prototipo del software che implementa alcune funzionalità.
Nell’ottica del modello Evolutionary sono stati introdotti i cosiddetti linguaggi a oggetti.
Tra questi uno dei più utilizzati è proprio il C++.
Esso presenta la caratteristica di poter essere usato sia secondo una metodologia orientata alla funzione, come in realtà abbiamo fatto finora, sia secondo una metodologia più propriamente orientata agli oggetti OOD (Object Oriented Design).
Terminologia della programmazione a oggetti
Introduciamo alcuni termini che si usano nella programmazione a oggetti.
Esempi di oggetti sono le persone, gli animali, le piante, le automobili, gli aerei, i palazzi, i calcolatori e quant’altro.
Gli uomini pensano in termini di oggetti.
Possiamo suddividere gli oggetti in due grandi categorie: oggetti animati e inanimati. I primi si muovono e intraprendono azioni, al contrario dei secondi.
Entrambi i tipi di oggetti hanno una caratteristica in comune:
gli attributi, come ad esempio la dimensione, la forma, il colore, il peso, e così via.
Gli oggetti hanno tutti dei comportamenti: un pallone rotola, rimbalza, si gonfia o si sgonfia; un bambino piange, dorme, mangia, cammina; un’automobile accelera, frena, svolta; un asciugamano assorbe l’acqua e così via.
Nella programmazione a oggetti si devono individuare i tipi di attributi e i comportamenti che caratterizzano gli oggetti software individuati.
Molti oggetti diversi possono essere caratterizzati da attributi e comportamenti molto simili tra loro. Ad esempio un bambino e un adulto hanno comportamenti simili per alcuni versi e diversi per altri. Oppure se pensiamo alla classe dei felini avremo animali come i gatti e le tigri che sono simili per alcuni aspetti e molto diversi per altri.
L’idea sottointesa dalla progettazione ad oggetti è quella di modellare gli oggetti software sulla base degli oggetti del mondo reale con i loro attributi e modalità di funzionamento.
E’ stato quindi introdotto il concetto di classe che individua tutte le proprietà che caratterizzano in genere una larga varietà di oggetti, che si diranno appartenere a quella classe.
Ad esempio alla classe delle sedie appartengono tutti quegli artefatti che consentono ad un uomo di sedere; hanno quindi una certa altezza massima, hanno un piano di appoggio, sono spostabili, e così via. Quindi alla classe sedia appartengono le poltrone, le sedie a sdraio, gli sgabelli e così via.
Si introducono poi delle relazioni di ereditarietà, secondo le quali è possibile definire nuove classi di oggetti derivate da classi esistenti, ereditando le loro caratteristiche ed estendendole con caratteristiche proprie.
Ad esempio, un oggetto della classe mucca ha le stesse proprietà degli oggetti appartenenti alla classe mammifero in aggiunte ad altre proprietà che la caratterizzano, quali ad esempio quella di produrre latte.
Programmando ad oggetti abbiamo uno strumento più naturale e intuitivo di pensare al processo della programmazione, in quanto possiamo modellare, con la precisione che ci interessa, gli oggetti del mondo reale, i loro attributi e i loro comportamenti.
Con la programmazione ad oggetti è anche consentita una comunicazione tra gli oggetti che avviene mediante messaggi, che reciprocamente possono inviarsi.
Un oggetto prenotazione ferroviaria, potrebbe, ad esempio, ricevere un messaggio che gli impone di ridurre il numero di posti disponibili su un determinato treno, a seguito di un messaggio inviato da un altro oggetto denominato biglietto e relativo ad una prenotazione effettuata.
La progettazione ad oggetti incapsula i dati (gli attributi) e le funzioni (i comportamenti) in pacchetti denominati oggetti; tali dati e funzioni sono intimamente correlati tra di loro.
E’ possibile per un oggetto mascherare delle informazioni (information hiding). Infatti, sebbene gli oggetti comunichino tra loro attraverso interfacce ben definite, non sempre un oggetto conosce la struttura interna degli oggetti con cui comunica, in quanto i dettagli delle implementazione sono nascosti all’interno di ciascun oggetto.
Ad esempio possiamo tranquillamente adoperare un calcolatore anche se non conosciamo come esso funzioni.
Si hanno quindi i linguaggi come il C, il Pascal e così via detti linguaggi procedurali, nei quali la programmazione tende a essere orientata all’azione.
Al contrario la programmazione nei linguaggi come il C++ è detta orientata agli oggetti.
Mentre in C l’unità di programmazione è la function, in C++ l’unità è la classe, dalla quale eventualmente si istanziano (creano) gli oggetti.
La relazione fra classi e oggetti può essere esemplificata come segue:
Il progetto di un’automobile sta all’automobile costruita, allo stesso modo in cui le classi stanno agli oggetti.
Infatti, così come è possibile costruire molte automobili partendo dallo stesso progetto allo stesso modo, possiamo istanziare parecchi oggetti a partire da una sola classe.
I programmatori C si concentrano sulla scrittura di funzioni, cioè di gruppi di azioni che effettuano un’operazione completa.
Un programma è costituito da un insieme di funzioni.
I dati sono importanti anche in C, naturalmente, ma il concetto è che essi sono soltanto un supporto alle azioni da intraprendere.
I verbi presenti nelle specifiche (descrizioni) di un sistema guidano il programmatore C nel determinare le funzioni che devono lavorare insieme per implementarlo.
Le classi del C++ contengono funzioni che implementano le operazioni e i dati che implementano gli attributi.
Una progettazione orientata agli oggetti OOD (Object Oriented Design) focalizza l’attenzione sul dominio del problema più che sui processi inerenti la sua soluzione.
In altre parole gli elementi di base sono ora gli oggetti e non più le funzioni.
Che cosa è un oggetto?
Ad esempio, il tipo int rappresenta il concetto di numero intero mentre le operazioni +, -, *, /, % rappresentano alcuni dei suoi metodi, cioè sono gli strumenti che possiamo utilizzare per operare con essi.
Quindi possiamo dire che il concetto di tipo parte da un’idea astratta per poi assumere una rappresentazione concreta.
Altro esempio. Abbiamo introdotto le strutture con la parola chiave struct, che ci hanno permesso di definire vari tipi, come studente, anagrafe, data etc., che si servono a loro volta sia di tipi elementari che di altre strutture.
Ora, così come accade per i tipi int e float, potremmo definire delle operazioni anche per strutture tipo la struct studente.
In questo caso, per esempio, potremmo avere, come operazioni, o metodi, la lettura e scrittura dei dati dello studente, il calcolo della media dei voti e delle tasse pagate fino ad un certo periodo, e così via.
Per ottenere questo risultato è necessario introdurre un nuovo costrutto che inglobi in sè sia i dati che le operazioni.
A tal fine si introduce un nuovo tipo utilizzando la parola chiave class.
La class è, quindi, un tipo definito dall’utente con annesse tutte le operazioni e le proprietà che lo rendono adatto agli scopi che l’utente stesso si prefigge.
Gli oggetti sono le variabili di tipo class: diremo anche che un oggetto è un’istanza della classe.
Per conservare la modularità, è necessario che ogni oggetto esegua il suo compito senza che l’utilizzatore conosca esattamente come ciò avvenga.
Questo si verifica sicuramente con i tipi standard, int e float: infatti noi eseguiamo su di loro tutte le operazioni del caso senza necessariamente sapere come queste operazioni sono implementate.
Con questo information hiding, si rendono invisibili dall’esterno tutti i meccanismi interni dell’oggetto.
Inoltre, l’oggetto contiene al suo interno tutte le proprietà e le operazioni che consentono di gestirlo: questo meccanismo di autosufficienza dell’oggetto è detto incapsulamento.
In definitiva un oggetto è qualcosa di autosufficiente caratterizzato da
Le proprietà e i metodi di un oggetto possono essere
pubblici (public) se sono sempre accessibili dall’esterno,
privati (private) se sono invisibili dall’esterno e quindi gestibili unicamente dagli oggetti stessi,
protetti (protected) se sono pubblici per gli oggetti derivati e privati per quelli astratti.
La tipologia protected è connessa all’ereditarietà ed al polimorfismo, aspetti che non tratteremo in questo corso.
Un oggetto è un insieme indistinto di variabili e comportamenti.
Un oggetto è la definizione di un nuovo tipo, che rappresenta la sua classe (class).
Un oggetto-classe definisce una particolare variabile che lo contiene insieme a tutti i comportamenti che può esibire.
Le variabili associate con una classe sono dette data-member (istanze di variabili) e i comportamenti sono detti function-member (o metodi).
I metodi sono i processi che possono essere attivati all’interno della metodologia orientata agli oggetti; essi corrispondono alle function della metodologia Top-down.
Nella progettazione orientata agli oggetti i dati devono corrispondere agli elementi concettuali che meglio possono essere utili per la soluzione del problema proposto.
Le classi, contenendo tipi primitivi, tipi di dati definiti dall’utente, come le struct, e le operazioni relative, consentono al compilatore di verificare eventuali inconsistenze tra specifiche, implementazione ed uso grazie al concetto di astrazione in esse contenuto.