Progettazione Orientata agli Oggetti

Il materiale utilizzato per questa lezione è il capitolo dell'Horstmann intitolato "Progettazione Orientata agli oggetti", scaricabile qui ProgOO_Java2.pdf.

Argomenti affrontati:

  • Il ciclo di vita del software;

  • Identificare classi, responsabilità e collaborazioni; Uso delle Schede CRC (Class, Responsability, Collaboration)

  • Individuare e documentare le relazioni tra classi:

    • ereditarietà (è-un);
      Esempi: Studente - Persona, Cerchio - Figura2D, Vocabolario - Libro, ...

    • associazione (ha-un);
      Esempi: Automobile - Ruota, Persona - Indirizzo, Cerchio - Punto, ...

    • dipendenza (usa); Esempi: Input - System, Persona - Indirizzo, ...

    • implementazione; Esempi: ArrayList - List, String - Comparable, ...

  • Diagrammi UML

  • Esempio di progettazione di un semplice sistema:

    • Stampare una fattura




Realizzazione di sistemi software complessi

Tutte le esercitazioni che abbiamo svolto riguardavano la soluzione di piccoli problemi mirati a fare acquisire dimestichezza con le primitive del linguaggio e con un corretto stile di programmazione. L'esercitazione vi vedeva occupati soprattutto a digitare codice sorgente e correggere errori.

Per realizzare con successo un "vero" sistema software, oltre alla programmazione è indispensabile un certo impegno per la pianificazione, la progettazione e il collaudo, tutte fasi che finora abbiamo limitato o addirittura trascurato.

Per esempio, nel caso di progetti di grandi dimensioni, la quantità di tempo dedicata alla pianificazione dovrebbe superare di gran lunga il tempo impiegato nella programmazione e per il collaudo: carta e penna al posto di video e tastiera.

Anche in progetti di dimensioni minori (come quelli che verranno assegnati a conclusione del corso), procedendo in modo sistematico potreste evitare errori e risparmiare tempo prezioso.




Il ciclo di vita del software

Molti ingegneri del software scompongono il processo di sviluppo nelle seguenti cinque fasi (tralasciando le fasi di assistenza sul prodotto e di ritiro dal mercato quando il prodotto diviene obsoleto):
  1. Analisi (raccolta di requisiti): si decide che cosa il progetto debba realizzare (ma non come realizzarlo). In che modo l'utente utilizzerà l'applicazione? Quanti e quali dati in ingresso devono poter essere gestiti? Quali fabbisogni nelle prestazioni in termini di tempo e spazio (di memoria e su disco)?

    Risultato: un elenco di requisiti.

  2. Progettazione (piano di attuazione): Quali strutture e classi servono? Quali metodi offrono? Con quali argomenti?

    Risultato: descrizione e diagrammi delle classi.

  3. Implementazione (realizzazione al calcolatore): stesura del codice sorgente seguendo le linee guida tracciate dalla progettazione.

    Risultato: programma che compila correttamente.

  4. Collaudo (test e debug): si eseguono delle prove per verificare che il programma funzioni correttamente. È importante effettuare esperimenti con dati non conformi alle specifiche per verificare la robustezza del sistema.

    Risultato: documento che descrive le prove effettuate.

  5. Installazione (deployment): il programma viene consegnato agli utenti.

La dipendenza causale tra queste fasi è evidente (modello a cascata), ma nella pratica difficilmente una fase può considerarsi del tutto chiusa prima di passare alla successiva: Per esempio, collaudi disastrosi potrebbero implicare la riprogettazione del sistema. Il modello a spirale sfrutta la costruzione di prototipi via via più completi e raffinati.




Gruppi di lavoro

Nella professione di programmatore (ma non solo) imparare a lavorare bene in gruppo conta quasi quanto la competenza tecnica, perché ogni progetto veramente importante trascende le capacità di qualunque singolo programmatore.

Allo stesso modo, una buona progettazione è indispensabile per portare avanti un lavoro svolto da un team di programmatori. In questi casi anche l'aggiornamento costante di tutta la documentazione di riferimento e più in generale la comunicazione tra gli sviluppatori diviene cruciale.

Supponiamo che un certo programmatore, Paolo Rossi, sia in grado di realizzare e collaudare 100 metodi al mese. (Questa è una stima parecchio generosa: sono pochi i programmatori così produttivi.)

Supponiamo che un progetto di medie dimensioni richieda 10.000 metodi. Allora Paolo avrebbe bisogno di 100 mesi per portare a termine il lavoro (si parlerebbe di un progetto da 100 mesi/uomo).

Ma attenzione! Il concetto di mese/uomo è un mito: Mesi e programmatori non sono intercambiabili.

Cento programmatori non riuscirebbero a finire il lavoro in un solo mese per mancanza di conoscenza sulle attività degli altri colleghi.

Anche solo dieci programmatori probabilmente non riuscirebbero a completarlo in 10 mesi:

  • Prima di diventare produttivi devono conoscere il progetto.
  • Tutte le volte che un metodo presenta qualche problema, il suo autore deve riunirsi con gli altri programmatori e discuterne, magari bloccando il lavoro di molti.
  • Quando un progetto è in ritardo rispetto alle tempistiche preventivate, l'aggiunta di altro personale rischia solo di creare ulteriori ritardi, perché le persone più indicate per l'addestramento dei nuovi arrivati sono proprio quelle più produttive.



Progettazione orientata agli oggetti

Data una certa attività da realizzare, il processo di progettazione orientato agli oggetti comprende sostanzialmente le le seguenti operazioni:
  1. Identificare le classi: Una semplice regola per trovare le classi consiste nel cercare i sostantivi nella descrizione dell'attività da realizzare.

  2. Determinare il comportamento di ciascuna classe: Una volta definite le classi, bisogna capire quali metodi ciascun oggetto delle classi deve eseguire per portare a buon fine l'attività. Una semplice regola per trovare questi metodi consiste nel cercare i verbi nella descrizione dell'attività e quindi mettere i verbi in corrispondenza con gli oggetti appropriati, determinando quale classe è responsabile di un certo metodo. Questo compito è facilitato dall'uso di schede CRC.

  3. Descrivere le relazioni fra le classi: In presenza di classi caratterizzate da un comportamento comune potrebbe essere conveniente astrarre tale comportamento in una superclasse (o interfaccia) comune. Lo sviluppo di classi totalmente scorrelate potrebbe essere affidato a programmatori diversi. Però l'ereditarietà e l'implementa non sono le uniche relazioni importanti. Conviene considerare anche le associazioni (ogni oggetto è associato agli oggetti memorizzati nelle sue variabili di istanza) e le dipendenze (la classe A dipende dalla classe B se uno dei metodi definiti in A usa un oggetto di B in qualche modo). Questo compito è supportato dal disegno di diagrammi UML.

Nota: Ogni associazione ("ha un") è evidentemente anche una dipendenza ("usa") particolare.

Nella fase di implementazione ricordate anche di utilizzare da subito javadoc per documentare le vostre classi, anche se si tratta solo di prototipi.




Stampa di una fattura

Vediamo come sia possibile applicare questi concetti su un esempio specifico.


REQUISITI

Questo programma ha lo scopo di stampare una fattura.

Una fattura descrive la somma dovuta per un insieme di prodotti con le rispettive quantità. (Trascuriamo aspetti complessi quali le date, le imposte e i codici della fattura e dei clienti.)

Il programma deve stampare l'indirizzo di chi deve pagare, l'elenco degli articoli e l'importo da pagare.

Ciascuna linea che descrive un articolo ne contiene la descrizione e il prezzo unitario, nonché la quantità ordinata e il prezzo totale.


                F A T T U R A 

Piccoli Elettrodomestici Aldo 
via Nuova,100
Turbigo,MI 20029


Articolo            Q.ta   Prezzo (EUR)   Totale (EUR)

Tostapane             3       29,95          89,85 
Asciugacapelli        1       24,95          24,95 
Spazzola elettrica    2       19,99          39,98 

IMPORTO DOVUTO: 154,78  EUR

Per semplicità, non forniremo alcuna interfaccia utente, ma predisporremo un programma di prova che aggiunge articoli alla fattura e la stampa.




Schede CRC (1)

Come prima cosa dobbiamo individuare le classi utili.

Cerchiamo i sostantivi all'interno della formulazione del problema:


REQUISITI

Questo programma ha lo scopo di stampare una fattura.

Una fattura descrive la somma dovuta per un insieme di prodotti con le rispettive quantità. (Trascuriamo aspetti complessi quali le date, le imposte e i codici della fattura e dei clienti.)

Il programma deve stampare l'indirizzo di chi deve pagare, l'elenco degli articoli e l'importo da pagare.

Ciascuna linea che descrive un articolo ne contiene la descrizione e il prezzo unitario, nonché la quantità ordinata e il prezzo totale.

Per semplicità, non forniremo alcuna interfaccia utente, ma predisporremo un programma di prova che aggiunge articoli alla fattura e la stampa.


Fattura, Indirizzo, Articolo (della fattura), Prodotto, Descrizione, Prezzo (unitario), Quantità, (prezzo) Totale, Somma (complessiva)



Schede CRC (2)

Alcuni dei sostantivi rappresentano valori, non oggetti:

Fattura, Indirizzo, Articolo (della fattura), Prodotto, Descrizione, Prezzo (unitario), Quantità, (prezzo) Totale, Somma (complessiva)


Restiamo con quattro candidati per le classi: Fattura, Indirizzo, Articolo (della fattura), Prodotto.

Ciascuno di loro rappresenta un concetto utile, quindi facciamoli diventare tutti classi.




Schede CRC (3)

Adesso dobbiamo determinare il comportamento di ciascuna classe.

Cerchiamo i verbi all'interno della formulazione del problema:


REQUISITI

Questo programma ha lo scopo di stampare una fattura.

Una fattura descrive la somma dovuta per un insieme di prodotti con le rispettive quantità. (Trascuriamo aspetti complessi quali le date, le imposte e i codici della fattura e dei clienti.)

Il programma deve stampare l'indirizzo di chi deve pagare, l'elenco degli articoli e l'importo da pagare.

Ciascuna linea che descrive un articolo ne contiene la descrizione e il prezzo unitario, nonché la quantità ordinata e il prezzo totale.

Per semplicità, non forniremo alcuna interfaccia utente, ma predisporremo un programma di prova che aggiunge articoli alla fattura e la stampa.


Queste attività sono troppo astratte, dobbiamo raffinarle:
  • Il verbo stampare è troppo generico (a video, su stampante, su file, ...) quindi interpretiamolo come restituzione di una stringa opportunamente formattata.
  • La stampa della fattura comprende la stampa degli elementi che la compongono e del totale.
  • Solitamente una classe è responsabile della stampa dei propri elementi, quindi ogni classe identificata avrà un metodo String format().
  • Per calcolare il totale servono i totali di ogni articolo (che vengono sommati). La classe Articolo deve quindi fornire un metodo che restituisca il costo dell'articolo ottenuto moltiplicando la quantità per il costo unitario del corrispondente prodotto. Restituire il costo del prodotto è ovviamente responsabilità della classe Prodotto.
  • Infine, come anticipato nei requisiti, la classe Fattura deve fornire un metodo per aggiungere articoli.



Schede CRC (4)

Fattura
formattazione della fattura Indirizzo
  Articolo
   
aggiunta di un articolo Articolo
   
   
   


Indirizzo
formattazione dell'indirizzo  
   
   
   
   
   


Articolo
formattazione dell'articolo Prodotto
   
fornire il prezzo totale Prodotto
   
   
   


Prodotto
formattazione del prodotto  
   
fornire il prezzo unitario  
   
   
   



Diagrammi UML

Le relazioni di dipendenza si ricavano dalla colonna delle collaborazioni nelle schede CRC: Ciascuna classe dipende dalle classi con le quali collabora.

Nel nostro esempio:

  • Fattura collabora con Indirizzo, Articolo e Prodotto.
  • La classe Articolo collabora con la classe Prodotto.

Ma come fa una fattura a conoscere l'indirizzo, gli articoli e i prodotti con cui collabora? Non è che queste dipendenze sono in realtà associazioni?

  • Fattura è associata con Indirizzo e Articolo.
  • Articolo è associata con Prodotto.

In questo esempio non emerge nessuna relazione di ereditarietà.

Come esercizio, completate l'esempio della fattura implementando le classi prima di guardare la soluzione proposta da Horstmann.