Sie sind auf Seite 1von 22

Sessione 11

Progetto “Corso”

1
Il progetto “Corso”
In questa sessione introdurremo il progetto d’esempio che verrà utilizzato nel prosieguo del corso come riferimento per illustrare
le fasi operative tipiche inerenti la realizzazione di un progetto in Java.

Useremo un progetto estremamente semplificato ma che comunque ci darà modo di mostrare le fasi tipiche che vengono seguite
nella realizzazione di un progetto reale, partendo dall’analisi fino all’implementazione finale.

Come accade in un progetto reale, il nostro sistema potrebbe subire modifiche in corso d’opera e, sempre come accade nella
realtà, il progetto sarà costruito progressivamente seguendo un modello concettuale che potremmo schematizzare come:

• Analisi requisiti

• Modellazione iniziale

• Analisi ed implementazione del modello ad oggetti

• Analisi ed implementazione del modello di accesso ai dati

• Analisi ed implementazione delle funzionalità principali del progetto (business logic)

• Analisi ed implementazione dell’interfaccia grafica, web-based, dell’applicazione

2
E’ chiaro che ciascuna delle suddette fasi sarà molto compressa, in modo da mostrare solo le parti salienti e soprattutto di
interesse didattico.

Già da un’attenta osservazione dei punti elencati sopra, si dovrebbe intuire la natura “progressiva” nell’implementazione
dell’applicazione a cui facevamo riferimento prima. E’ evidente, infatti, che una serie di concetti non sono stati ancora affrontati e
pertanto la relativa fase di analisi ed implementazione avverrà successivamente nel corso. Ci riferiamo in particolare,
all’interfaccia grafica web-based, ma anche alla parte database che in teoria potrebbe essere già implementata basandosi sui
concetti esposti nelle precedenti sessioni sfruttando Jdbc. In realtà, per la parte database non useremo Jdbc (la cui conoscenza
resta comunque fondamentale per un programmatore Java), ma utilizzeremo Hibernate. Di conseguenza anche l’implementazione
della parte database verrà affrontata successivamente. Tuttavia, quanto appena detto, ci darà l’occasione per mettere in pratica
alcuni concetti estremamente interessanti mostrati quando parlammo di interfacce e classi astratte, ovvero la possibilità di iniziare
sin da ora a definire cosa vogliamo fare senza essere costretti a decidere sin da adesso come lo vogliamo fare. Vale a dire
l’essenza del concetto di interfaccia: dichiarare la firma di un metodo senza implementazione.

Inoltre, nel corso della sessione avremo anche modo di mettere in luce altri aspetti fondamentali che un programmatore Java deve
conoscere: l’uguaglianza di oggetti ed i tipi di relazioni tra oggetti.

I punti elencati sopra rappresentano soltanto una vista semplificata di un processo abbastanza complesso che va sotto il nome di
ingegneria del software, ovvero la descrizione di tutto il ciclo di vita del software, ovvero raccolta requisiti-analisi-disegno-

3
implementazione-test etc. che in teoria dovrebbe seguire qualunque progetto software (non soltanto Java, ovviamente). Si tratta di
un tema estremamente vasto sul quale esiste una bibliografia molto ampia ed esami universitari ad hoc. Tra l’altro è un tema
sempre molto attuale ed in continua evoluzione in quanto di continuo vengono proposte nuove tecniche che in teoria dovrebbero
aumentare la produttività riducendo i tempi necessari tra l’avvio del progetto e la messa in produzione, o in esercizio, dello
stesso.

Anche in questo caso, come già detto in altre sessioni non ci dilungheremo affatto su tutta questa tematica in quanto non è
obiettivo del corso. In questa sede si vuole soltanto dare una visione sintetica di quelle che sono alcune delle fasi che di fatto
segue sempre la messa in esercizio di un’applicazione reale.

A titolo puramente informativo, indichiamo il modello water-fall (a cascata) che era considerato come punto di riferimento nei
manuali di ingegneria del software sino a pochi anni or sono ed i modelli agile, con l’uso di Scrum,Kanban, Lean, giusto per dare
qualche nome che va per la maggiore negli ultimi anni.

Secondo la metodologia water-fall, le varie fasi del software si ripetono rigidamente e ciclicamente fino alla messa in esercizio
del sistema che comunque non chiude il ciclo di vita del software, in quanto successivamente si passa alla fase di manutenzione
del software; manutenzione correttiva, per correggere eventuali errori o bug del sistema ed evolutiva per l’implementazione di
nuovi requisiti richiesti dal cliente. Inoltre ciascuna fase del ciclo di vita del software vede come protagonisti attori diversi, i
cosiddetti stake-holders; ad esempio key-account e client-manager nella fase di raccolta dei requisiti iniziali presso il cliente,

4
analisti e designer nella fase di analisi e modellazione iniziale, sviluppatori nella fase di implementazione e tester nella fase di
test.

Secondo invece le più recenti metodologie agile, le varie fasi ed i vari attori vengono “collassati”: i vari attori partecipano
contemporaneamente alle varie fasi che seguono uno sviluppo molto più rapido, si potrebbe semplificare dicendo “piccoli passi
alla volta che coinvolgano tutti gli attori”.

5
I requisiti del sistema
Supponiamo di dover implementare un’applicazione che permetta di gestire i corsi didattici di un’azienda.

Il nostro committente è un’azienda distribuita su 3 sedi geografiche (Roma, Napoli e Milano) che eroga corsi di varia natura
(informatica, lingue, etc.), che gestisce diversi docenti, diversi discenti e aule distribuite sulle 3 sedi aziendali.

L’applicazione dovrà consentire le operazioni di creazione, aggiornamento, cancellazione e ricerca sulle anagrafiche dei docenti,
discenti, aule e corsi. Inoltre, tutte le operazioni appena descritte dovranno essere fruibili attraverso un’opportuna interfaccia
grafica web-based. Il requisito, in sostanza, è quello di creare un’applicazione che esponga sul web tutte le funzioni
precedentemente descritte.

Il nostro cliente richiede anche che l’accesso all’applicazione web sia protetto tramite login con utente e password.

Inoltre il nostro cliente richiede di gestire anche la profilazione degli utenti: un profilo amministrativo che può fare tutto ed un
profilo utente che può soltanto visualizzare le informazioni dell’applicazione ma non modificarle.

6
Il modello ad oggetti del sistema
Volendo modellare in termini object-oriented i requisiti sopra esposti, il primo passo da seguire è l’individuazione delle classi
principali necessarie per descrivere il sistema.

Prima ancora però possiamo iniziare a definire la struttura del nostro progetto in termini di package destinati ad ospitare le nostre
classi.

Struttura dei package

Come detto nelle prime sessioni introduttive una best-practice è creare come package radice del progetto un package del tipo
com.azienda.nomeProgetto. Volendo chiamare il nostro progetto “progettoCorso” useremo come nome del package radice
com.azienda.progettoCorso.

A partire da questo package, possiamo pensare di creare i seguenti sotto-package:

• model: destinato ad ospitare le classi che andremo a salvare sul database

• dao: destinato ad ospitare le classi che interfacciano direttamente il database per le operazioni CRUD (create-retrieve-
update-delete, ovvero creazione-ricerca-aggiornamento-cancellazione). dao è un acronimo inglese che sta per Data-Access-
Object

7
• businessLogic: destinato ad ospitare le classi che espongono le funzionalità del nostro sistema (in gergo si parla appunto
Business Logic)

• userInterface: destinato ad ospitare le classi che rappresentano l’interfaccia grafica del nostro sistema

Come detto prima, andando avanti potremo aggiungere/modificare/rimuovere alcune classi e/o package in funzione dell’analisi
del progetto che man mano evolverà.

Focalizziamoci adesso sulla vera e propria analisi delle classi.

Classi del package model

Sicuramente la nostra applicazione avrà bisogno delle seguenti classi: Materia, Aula, Docente, Discente, Sede, Corso e Utente.
Queste classi rappresentano i dati che andremo a memorizzare nel database ed avranno una controparte in altrettante tabelle
omonime nel nostro database. Queste classi le andremo ad inserire nel package model.

Vediamo adesso quali sono le informazioni (e quindi gli attributi) che contraddistinguono ciascuna delle classi del package
model.

Le classi Materia ed Aula potrebbero essere ben definite tramite un nome ed una descrizione.

8
Discente e Docente potremmo modellarle come un’unica classe Soggetto i cui attributi potrebbero essere nome, cognome,
codiceFiscale, dataNascita, comuneDiNascita, comuneDiResidenza, indirizzoResidenza, eMail, telefono e a cui potremmo
aggiungere un attributo isDocente per distinguere il docente dai discenti.

La classe Sede potrebbe essere definita attraverso gli attributi nome, descrizione, comune, indirizzo, eMail, telefono, isLegale.

L’ultimo attributo, isLegale, serve a distinguere le sedi operative da quella legale dell’azienda che eroga i corsi.

La classe Utente potremmo modellarla attraverso gli attributi userName, userPassword, profilo.

L’attributo profilo servirà a distinguere gli amministratori dell’applicazione dai normali utenti. In un progetto reale avremmo
definito una nuova classe (ed una nuova tabella) per rappresentare il profilo, ma, come detto, qui cerchiamo di non complicare
troppo il progetto.

Infine la classe Corso che definiremo attraverso gli attributi nome, descrizione, materia, aula, docente, discenti, dataInizio,
numeroSessioni.

Considerazioni sulle classi che modellano dati del database e sull’identità degli oggetti Java

Un’analisi object-oriented “pura” prescinde completamente dalla circostanza che gli oggetti e le classi che stiamo modellando
andranno o meno ad essere rappresentati sul database. Tuttavia, da un punto di vista pratico non è sempre così.

9
Quando modelliamo le classi che abbiamo elencato prima dovremmo avere un’idea di base del fatto che inevitabilmente queste
classi rappresentano dati che dovremo andare a salvare su database. E’ evidente, infatti, che i dati relativi ai corsi, alle sedi, agli
utenti ect. non è pensabile che vadano persi quando la nostra applicazione viene stoppata, devono necessariamente essere salvati
su database. E, da un punto di vista pratico, la considerazione appena fatta ci porta subito a modellare le classi in modo
leggermente differente rispetto al caso in cui stiamo modellando classi che sappiamo non rappresentare oggetti da salvare su
database. Tanto per cominciare una caratteristica che devono avere le classi che rappresentano oggetti da inserire su database è
che devono implementare l’interfaccia Serializable (cfr. sessione su input/output e serializzazione).

Sicuramente possiamo iniziare ad immaginare che avremo delle tabelle sul database Materia, Aula, Soggetto, Sede, Corso e
Utente che fanno da contraltare alle classi che abbiamo detto prima.

Iniziamo subito col dire che quando vogliamo salvare un oggetto su database, quello che dobbiamo fare è salvare lo stato interno
dell’oggetto sul db; ovvero salvare il valore degli attributi dell’oggetto in altrettanti campi di un tabella del database.

Ad esempio, se abbiamo definito una classe A con un solo attributo nome di tipo stringa e vogliamo salvare oggetti di classe A nel
nostro database, andremo a creare una tabella A sul nostro db con un campo nome di tipo character varying (nel caso di
PostgreSql) e, quando salveremo un oggetto di classe A nel database andremo ad inserire un record nella tabella A valorizzando il
campo nome della tabella A con il valore dell’attributo nome del nostro oggetto.

Apriamo adesso una parentesi fondamentale che riguarda il concetto di identità di un oggetto.

10
Sappiamo da quanto visto nelle precedenti sessioni sui database che il vincolo più importante nella definizione di una tabella è il
vincolo di primary key. La primary key è l’informazione principale usata dai DBMS per identificare univocamente un record
all’interno di una tabella. Ma se il record da salvare in tabella rappresenta un oggetto, come ad esempio un oggetto di classe
Materia, capiamo subito che il concetto di primary key è strettamente connesso al concetto di identità di un oggetto, ovvero al
meccanismo attraverso cui la JVM identifica univocamente un oggetto.

Potremmo fare questo parallelo: la primary key sta al DBMS come l’identità di un oggetto sta alla JVM.

A questo punto dovremmo chiederci come la JVM identifica un oggetto e qual è il meccanismo in base al quale per la JVM due
oggetti (ovviamente della stessa classe) sono o non sono uguali.

Ad esempio, supponiamo di avere una classe A così definita:

public class A {

private String nome = null;

public String getNome() {

return nome;

11
public void setNome(String nome) {

this.nome = nome;

e supponiamo che all’interno di un metodo di un’altra classe scriviamo il seguente codice:

A oggetto1 = new A();

oggetto1.setNome(“Mario”);

A oggetto2 = new A();

oggetto2.setNome(“Mario”);

Gli oggetti oggetto1 e oggetto2 sono due istanze della stessa classe A con lo stesso stato interno: il valore dell’unico attributo
nome è infatti lo stesso. Tuttavia per la JVM i due oggetti sono diversi. Quanto detto può essere immediatamente verificato
perché il JDK mette a disposizione un metodo equals nella classe Object che sappiamo essere la classe genitore di tutte le classi
Java (cfr. JavaDoc API di Object); pertanto il metodo equals è applicabile su qualunque oggetto, dunque potremmo scrivere:

12
boolean b = oggetto1.equals(oggetto2);

Per come è stata scritta la classe A, il valore del booleano b sarà false, ovvero i due oggetti per la JVM sono diversi.

Questo perché la JVM per controllare se due oggetti sono uguali utilizza appunto il metodo equals insieme al metodo hashCode
della classe Object. Nella loro implementazione di base (appunto quella della classe Object) per i due metodi due oggetti sono
sempre diversi perché sono riferimenti di memoria distinti, anche se sono oggetti della stessa classe e se il loro stato interno è
identico.

Tuttavia il JDK ci mette a disposizione il modo per cambiare questo meccanismo di controllo dell’identità di un oggetto:
l’override dei metodi equals ed hashCode.

I due metodi, infatti, non sono final e pertanto possono essere overriden (sovrascritti) dalle classi che ereditano da Object, ovvero
da tutte le classi Java.

Nell’esempio mostrato sopra potremmo quindi fare override dei due metodi, per esempio stabilendo che per il nostro codice due
oggetti di classe A sono uguali se hanno lo stesso stato interno, ovvero lo stesso valore dell’attributo nome.

Di seguito viene mostrato il codice sorgente con l’override dei metodi equals e hashCode (Eclipse, come mostrato nel video
allegato a questa sessione, aiuta molto nella scrittura di questi metodi, in quanto prevede un’opzione che permette di scrivere i

13
due metodi in automatico indicando soltanto qual è l’attributo su cui vogliamo lavorare per identificare gli oggetti, nel nostro
esempio sarà l’unico attributo nome):

@Override

public boolean equals(Object obj) {

if (this == obj)

return true;

if (obj == null)

return false;

if (getClass() != obj.getClass())

return false;

B other = (B) obj;

if (nome == null) {

if (other.nome != null)

14
return false;

} else if (!nome.equals(other.nome))

return false;

return true;

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + ((nome == null) ? 0 : nome.hashCode());

return result;

15
Dopo aver fatto l’override dei metodi equals e hashCode nel modo appena mostrato, se confrontiamo due oggetti della stessa
classe con lo stesso valore dell’attributo nome, osserveremo che per la JVM i due oggetti saranno uguali.

Quanto appena esposto vale in generale per l’identità di qualsiasi oggetto Java.

Se stiamo modellando una classe che rappresenta oggetti che andranno salvati su database diventa molto intuitivo pensare di
basare l’identità degli oggetti, ovvero l’override dei metodi equals e hashCode, sull’attributo (o gli attributi) che avrà il proprio
corrispettivo nel campo (o i campi) della primary key della tabella del database. Nell’esempio mostrato prima, se volessimo
salvare gli oggetti di classe A sul database, andremo a creare una tabella A con un campo nome di tipo character varying e
definiremo una primary key sul campo nome della tabella, mentre definiremo l’identità degli oggetti di classe A facendo override
dei metodi equals e hashCode basandoci sull’attributo nome.

Quindi tornando al nostro modello dati, sicuramente in tutte le classi del package model dovremo fare l’override dei metodi
equals e hashCode (e ricordiamoci sempre che tutte le classi del package devono implementare Serializable). Il punto adesso è
decidere su quale attributi basare l’identità degli oggetti delle classi del package model; ma, come detto, questo in un certo senso
equivale a dire su quali campi delle omologhe tabelle vogliamo definire le primary key.

Riprendendo un concetto esposto nella sessione dedicata ai concetti base dei database, una prima scelta da fare per ciascuna delle
nostre tabelle è definire la primary key come chiave naturale o come sequence.

16
Abbiamo detto che la best-practice è quella di usare sempre le sequence eccetto i casi di tabelle molto semplici dove siamo sicuri
che uno dei campi della tabella permette sempre di identificare univocamente i record della tabella.

Nel nostro caso, potremmo definire le primary key delle tabelle Corso, Docente, Discente e Sede attraverso una sequence (quindi
un numerico) mentre per le tabelle Aula, Materia e Utente potremmo usare una chiave naturale sul campo nome (per noi il nome
è sufficiente per distinguere univocamente le aule e le materie, supponendo che non ci siano aule con lo stesso nome nelle diverse
sedi del cliente) e sul campo userName per Utente.

Questo ci porta quindi a definire l’identità degli oggetti (ovveride di equals e hashCode) per le classi Aula, Materia ed Utente
sugli attributi nome, nome e userName, mentre per le altre classi dovremo aggiungere un nuovo attributo numerico che potremmo
chiamare id e basare su quest’attributo l’override dei metodi equals ed hashCode.

Osserviamo però che un’altra best-practice per le classi che rappresentano oggetti deputati ad essere inseriti in tabelle con chiavi
naturali come primary key (quindi non con sequence) su database è quella di utilizzare una classe-wrapper che rappresenti
l’identificativo dell’oggetto in luogo dell’attributo. Ovvero, definiremo le classi MateriaId, AulaId, UtenteId.

MateriaId, per esempio, avrà solo l’attributo nome, ovvero l’attributo che per noi serve ad identificare un oggetto di classe
Materia.

L’override dei metodi equalsi ed hashCode in MateriaId sarà basato sull’attributo nome.

17
Materia, invece avrà un attributo id di classe MateriaId al posto dell’attributo nome e l’ovveride dei metodi equals e hashCode
sarà basato sull’attributo id.

Inoltre, per comodità, nella classe Materia introduciamo dei cosiddetti metodi-acceleratori, getNome e setNome che recuperano il
valore di nome dall’oggetto id. Sono detti acceleratori perché rappresentano delle scorciatoie: senza di essi per recuperare il
valore dell’attributo nome da un oggetto di classe Materia dovremmo prima recuperare un riferimo a MateriaId. In pratica questi
metodi ci consentono di scrivere:

Materia oggetto = new Materia();

String nome = oggetto.getNome();

al posto di:

Materia oggetto = new Materia();

String nome = null;

if ( oggetto.getId() != null )

nome = oggetto.getId().getNome();

18
}

Come si può vedere dal codice appena mostrato i metodi permettono di risparmiare linee di codice (perciò scorciatoie) nelle
classi chiamanti.

Le stesse osservazioni fatte adesso per Materia e MateriaId si ripetono in modo identico per le classi Aula ed AulaId e per Utente
ed UtenteId, salvo per il fatto che in quest’ultima classe il wrapper sull’identificativo va creato sull’attributo userName in luogo
di nome.

Invece per le altre classi, Docente e DocenteId, Discente e DiscenteId, Sede e SedeId, Corso e CorsoId andremo solo ad
aggiungere un nuovo attributo id di tipo Long (numero intero esteso, quindi molto grande) che rappresenterà l’equivalente Java
della sequence come primary key della tabella (la sequence è infatti un numerico grande) e baseremo l’override dei metodi equals
e hashCode sull’attributo id.

In conclusione notiamo, riepilogandole, tutte le modifiche che abbiamo applicato alle classi che rappresentano oggetti da inserire
su database:

• implementazione di Serializable nella dichiarazione della classe

• definizione di una classe-wrapper sull’attributo/i identificativo/i della classe o aggiunta dell’attributo numerico id

• rimozione dell’attributo/i identificativo/i dalla classe e inserimento del riferimento alla classe-wrapper

19
• override dei metodi equals e hashCode nella classe-wrapper basati sul campo identificativo dell’oggetto

• override dei metodi equals e hashCode nella classe di partenza basati sul riferimento alla classe-wrapper o sul campo id

• inserimento (per comodità) dei metodi-acceleratori nella classe di partenza

Relazioni e cardinalità delle relazioni tra classi (e tabelle)

Un altro concetto fondamentale nella modellazione delle classi di un sistema è la relazione che eventualmente può sussistere tra
le classi del sistema.

Prendiamo in considerazione la classe Corso: tra gli attributi con cui abbiamo modellato la classe abbiamo inserito:

• private Materia materia

• private Sede sede

• private Aula aula

• private Soggetto docente

Sono tutti esempi di relazioni di cardinalità n->1 (n ad uno) tra la classe Corso e le altre classi.

20
Guardiamo la relazione Corso->Aula, è una relazione n->1 perché un corso ha una sola aula, ma un’aula può essere usata da n
corsi differenti.

Quando le classi di cui analizziamo le relazioni sono classi che modellano i dati del database, come per tutte le classi del package
model, la relazione n->1 tra Corso ed Aula diventa:

• in Java, un attributo di tipo Aula inserito nella classe Corso. Potremo avere, infatti, n oggetti di classe Corso con lo stesso
valore dell’attributo aula (concettualmente n corsi che si svolgono nella stessa aula), ma dato un singolo oggetto di classe
Corso esisterà un solo valore possibile per l’attributo aula (concettualmente un corso ha una sola aula, nel nostro modello)

• sul database, una foreign key dalla tabella Corso alla tabella Aula. La foreign key della tabella Corso referenzia la primary
key della tabella Aula, quindi dato un record della tabella Corso esisterà un sol record della tabella Aula, identificato dalla
primary key di Aula

Pensiamo adesso alla relazione tra un corso ed i discenti. Un corso ha n discenti, ma d’altra parte un singolo discente può
partecipare ad m corsi. Questa è una relazione n->m (n ad m), ovvero con cardinalità multipla da entrambi i lati della relazione.

Una relazione n->m come quella tra Corso e Soggetto (i discenti sono oggetti di classe Soggetto) si traduce:

• in Java, in un attributo di tipo Collection, come un Set o una List, sia nella classe Corso che nella classe Soggetto

21
• sul database, nella creazione di una nuova tabella, detta associativa, che unisce le tabelle Corso e Soggetto. La tabella
associativa sarà costituita dalla coppia (primary key di Corso) + (primary key di Soggetto) e avrà una foreign key sul suo
campo (primary key di Corso) che referenzierà la primary key della tabella Corso + una foreign key sul proprio campo
(primary key di Soggetto) che referenzierà la primary key della tabella Soggetto

Per una trattazione completa si rimanda al video allegato a questa sessione.

Continuando i package descritti all’inizio possiamo già in questa fase iniziare a prevedere nel package dao un’interfaccia Dao
con i 4 metodi CRUD che lavorano su ciascuna delle classi del package model. Ovvero potremo avere metodi del tipo:

public Materia createMateria(Materia materia);

public List<Materia> retrieveMateria(Object parametriDiRicerca);

public Materia updateMateria(Materia materia);

public void deleteMateria(Materia materia);

e così vià per i metodi CRUD che lavorano sulle altre classi del package model.

Questi metodi e i successivi package li analizzeremo più avanti nel corso.

22

Das könnte Ihnen auch gefallen