Introduzione alla progettazione efficace ed agile del software
Progettazione e Sviluppo del Software
versione stampabile
Outline
Goal della lezione
- Riassumere i concetti, principi, meccanismi visti in precedenza
- Introdurre le basi della progettazione del software
- Introdurre le basi dell’agile
- Mostrare alcune applicazioni notevoli (pattern) dei concetti, principi, e meccanismi OO visti in precedenza
Argomenti
- Introduzione ai design pattern
- Architettura e design di dettaglio
- Agile software development: cos’è, manifesto, principi
- Pattern Strategy e Factory
Riassunto delle puntate precedenti
Elementi di progettazione di sistemi software (OO)
- Programma vs. sistema software
- Fasi processo di sviluppo: analisi $\to$ design $\to$ implementazione $\to$ collaudo $\to$ deployment
- Problem space (dominio/logica business) vs. solution space (scelte realizzative)
- concetto di astrazione e di “livello di astrazione”
- Riuso mediante
- utilizzo di altri oggetti (composizione)
- estensione dei servizi offerti da altri oggetti (ereditarietà)
- Concetti di modularità, dipendenza, accoppiamento, coesione
- Metodologie
Strumenti
- Java 17
- write once, run everywhere
- JDK: JRE (JVM [
java
] + JCL) + strumenti di sviluppo (javac
, jshell
…)
- Terminali per l’accesso al file system e ai programmi da linea di comando
- Strumenti JDK: compilazione con
javac
ed esecuzione con java
gradle
: build system
- plugin
java
, e blocco dependencies
git
: version control system
JUnit
: framework per lo unit testing
- Visual Studio Code: ambiente di sviluppo integrato (IDE)
Java: elementi di base
- Elementi di base
- programmazione strutturata/imperativa (C-like)
- programma: metodo pubblico statico
main
in classe pubblica
- tipi primitivi vs. tipi oggetto (classi/interfacce)
- variabili contengono i riferimenti ad oggetti (allocati nello heap)
- assegnamento per copia del riferimento
- oggetti allocati in memoria dinamica (heap) $\to$ lifetime di oggetti va oltre lo scope
- no deallocazione manuale (cf. GC)
Basi dell’OOP in Java
- Astrazione object-oriented
- oggetto = stato + comportamento + identità (cf. incapsulamento)
- interazione attraverso “scambio di messaggi” (in realtà: chiamate sincrone a metodi)
- classe come “tipo” di oggetti e come “template” di costruzione di oggetti simili (istanze)
- interfaccia vs. implementazione (cf. information-hiding)
- Costrutti
- dichiarazione
class
: contiene dichiarazione campi, metodi (statici o d’istanza), e costruttori
- uso di oggetti: accesso a proprietà (metodi/campi) mediante dot notation
- concetto di receiver di una method call e variabile speciale
this
- dichiarazione
interface
- modificatore
static
per codice statico (metodi/campi di classe)
- costruttori e inizializzazione di oggetti
- overloading di metodi e costruttori
- package come namespace e contenitori di classi (cf. dichiarazione
package
) organizzati gerarchicamente
- FQCN (Fully-Qualified Class Name)
import
per importare i nomi delle classi in scope
- controllo di accesso:
public
, package-private, private
- modificatore
final
per campi e variabili costanti (non modificabili)
OOP efficace
- Linee guida stilistiche di formattazione di sorgenti Java (spaziature, nomi, etc.)
- Incapsulamento
- impacchettamento dati e funzioni per la loro manipolazione
- information hiding: interfaccia pubblica stabile (contratto) e dettagli implementativi privati
- Uso interfacce per catturare astrazioni e contratti
- Varie
- metodi getter e setter
- oggetti immutabili
Competenza attuale attesa: costruzione di piccoli progetti software Java/Gradle, tracciati con controllo di versione (git), che realizzano ed esercitano (mediante programmi e test JUnit) semplici sistemi ad oggetti ben incapsulati
Introduzione alla progettazione del software
Richiamo: qualità interna vs. esterna
Qualità esterna – aspetti funzionali
- Realizza correttamente il suo compito
- In termini di quali funzionalità fornisce
Qualità esterna – aspetti non-funzionali
- Performance adeguate (alle specifiche)
- Uso efficiente/adeguato delle risorse del sistema (memoria, CPU)
- Caratteristiche di sicurezza, affidabilità, usabilità, etc..
Qualità interna – software ben costruito
- Facilmente manutenibile (leggibile, flessibile, riusabile)
- quindi: meno “costoso”, a breve-/medio-/lungo-termine
Come promuovere la qualità interna del software?
- Regole di stile e formattazione dei sorgenti
- Principi e tecniche di programmazione/progettazione efficace
- Uso di pratiche come il TDD
Il problema della progettazione
- Come organizzare in modo efficace un sistema OO (classi, interfacce, e relazioni tra queste) per ottenere la funzionalità e la qualità esterna desiderate?
Progettazione del software
Architettura
- Architettura del software: descrizione dei componenti principali e delle relazioni importanti tra questi
- esempio:
- una applicazione
Calcolatrice
consiste in 3 componenti principali:
- interfaccia grafica (GUI)
- componente che realizza i calcoli matematici
- controller: parte che richiede i calcoli a fronte delle azioni dell’utente e chiede alla GUI di visualizzare il risultato
- approfondiremo questa parte più avanti
Progettazione di dettaglio
- La progettazione di dettaglio descrive relazioni fra un insieme coeso di (tipi di) oggetti
- le relazioni più significative di un particolare sottosistema
- non descrive ogni singola classe/interfaccia del sistema
- documentata da più diagrammi UML di 5-10 classi ognuno
Agile software development
Introduzione
Il software è il risultato di un processo di sviluppo
- Processi, pratiche, e strumenti incidono sulla qualità del software
Qui introduciamo una famiglia di processi e pratiche nota con il termine agile
Agile software development (sviluppo agile del software)
- Lo sviluppo agile del software è un insieme di principi, pratiche, e metodi volti a rispondere al cambiamento
- cambiamento della comprensione del problema, dei requisiti, delle risorse…
- Si basa su approcci iterativi/incrementali a cicli brevi per feedback frequente, con focus su qualità attraverso pratiche/strumenti quali refactoring, pattern, …
Manifesto
https://agilemanifesto.org/
“Stiamo scoprendo modi migliori di creare software,
sviluppandolo e aiutando gli altri a fare lo stesso.
Grazie a questa attività siamo arrivati a considerare importanti:
- Gli individui e le interazioni più che i processi e gli strumenti
- Il software funzionante più che la documentazione esaustiva
- La collaborazione col cliente più che la negoziazione dei contratti
- Rispondere al cambiamento più che seguire un piano
Ovvero, fermo restando il valore delle voci a destra,
consideriamo più importanti le voci a sinistra.”
Agile: principi 1-12
- La nostra massima priorità è soddisfare il cliente
rilasciando software di valore, fin da subito
e in maniera continua.
- Accogliamo i cambiamenti nei requisiti,
anche a stadi avanzati dello sviluppo.
I processi agili sfruttano il cambiamento
a favore del vantaggio competitivo del cliente.
- Consegnamo frequentemente software funzionante,
con cadenza variabile da un paio di settimane a un paio di mesi,
preferendo i periodi brevi.
- Committenti e sviluppatori devono lavorare insieme
quotidianamente per tutta la durata del progetto.
- Fondiamo i progetti su individui motivati.
Diamo loro l’ambiente e il supporto di cui hanno bisogno
e confidiamo nella loro capacità di portare il lavoro a termine.
- Una conversazione faccia a faccia
è il modo più efficiente e più efficace per comunicare
con il team ed all’interno del team.
- Il software funzionante è il principale metro di misura di progresso.
- I processi agili promuovono uno sviluppo sostenibile.
Gli sponsor, gli sviluppatori e gli utenti dovrebbero essere in grado
di mantenere indefinitamente un ritmo costante.
- La continua attenzione all’eccellenza tecnica
e alla buona progettazione esaltano l’agilità.
- La semplicità - l’arte di massimizzare la quantità
di lavoro non svolto - è essenziale.
- Le architetture, i requisiti e la progettazione
migliori emergono da team che si auto-organizzano.
- A intervalli regolari il team riflette su come
diventare più efficace, dopodiché regola e adatta
il proprio comportamento di conseguenza.
Design Pattern e Progettazione di Dettaglio
Come progettare una buona classe o gruppo di classi?
- buona conoscenza della programmazione OO
- incapsulamento, information hiding
- polimorfismo
- ereditarietà (che vedremo)
- buona conoscenza di principi e pratiche di progettazione/programmazione efficace
- DRY (Don’t Repeat Yourself), …
- astrazione attraverso interfacce, tecniche di riuso (composizione), …
- utilizzo di cataloghi noti di pattern di progettazione (design pattern)
I Pattern di progettazione
- Un pattern è una soluzione notevole a problemi ricorrenti di design object-oriented
- I problemi di “design” sono spesso ricorrenti
- Progettisti esperti hanno nel tempo affrontato tali problemi, provato diverse soluzioni, fino ad ottenere soluzioni efficaci con chiara analisi di costi/benefici
- Tale “esperienza” è stata codificata in forma generica/riusabile in “pattern” che possono essere applicati in contesti simili
- Alcuni sono particolarmente famosi, come quelli della “Gang of Four” (detti anche Pattern GoF)
- Testo famosissimo (in C++): Design Patterns: Elements of Reusable Object-Oriented Software di E. Gamma, R. Helm, R. Johnson, J. Vlissides
- 23 in tutto. Esempi: Strategy, Decorator, Singleton, Template Method, Observer
- (Cit. “SW di grosse dimensioni li usano praticamente tutti”)
- Benefici
- Il loro uso migliora molto il codice
- Rende il codice più flessibile (nascono per questo)
- Portano più direttamente ad una buona organizzazione (delle responsabilità, delle dipendenze, etc.)
- Fanno parte di un “vocabolario” per la comunicazione fra programmatori/progettisti
Design Pattern in questa sede
Nel corso
- E’ importante imparare quelli che introdurremo
- E’ opportuno applicarli nel progetto d’esame (e documentarli nella relazione)
Per il vostro futuro
- Noi porremo le basi per uno loro studio in autonomia
- Un ottimo progettista li conosce e usa (ove opportuno) tutti
Motivazione: qualità interna, refactoring, agilità
- E’ importante conoscere e cercare di applicare i pattern in quanto:
- promuovono la qualità interna del software
- promuovono l’agilità attraverso software di qualità
- permettono di velocizzare l’attività di refactoring
Rifattorizzazione (refactoring)
- Operazione di modifica del codice che non aggiunge funzionalità
- Ha lo scopo di migliorare la qualità interna del SW
- la sua formattazione, la sua espressività semantica (cf. nomenclatura), la sua struttura
- Ha lo scopo di attrezzare il codice a possibili cambiamenti futuri (cf. agilità)
- Può/deve quindi comportare una riprogettazione di alcune parti
- Il refactoring è una pratica necessaria
- Una buona progettazione non la si ottiene al primo “colpo”, ma richiede vari refactoring
- Brian Foote identifica tre fasi nello sviluppo di un sistema: “prototyping”, “expansionary”, “consolidating”; nel consolidamento si rifattorizza
Il refactoring è supportato da test e pattern
- I test automatici permettono di verificare se un refactoring ha introdotto regressioni
- I design pattern forniscono direttamente “ricette” di buona costruzione o rifattorizzazione del SW
Struttura di un “pattern”
Un Pattern ha quattro elementi fondamentali
- Un nome.
- È un aspetto fondamentale! Entra a far parte di un vocabolario
- Un problema che risolve.
- La causa che porta al suo uso
- Include il contesto di applicazione del pattern
- La soluzione che propone.
- gli elementi del design e le loro responsabilità, relazioni con altri elementi, e collaborazioni
- La conseguenza che porta.
- risultati e vincoli (ad es. in termini di riuso, variabilità, performance, …)
- il risultato è spesso un trade-off
- la scelta di applicare un pattern è questione di costi-benefici
Classificazione dei Pattern: categorie
Livello “proposito del Pattern”
- Creazionali: Riguardano la creazione degli oggetti
- Strutturali: Riguardano la composizione di classi/oggetti
- Comportamentali: Riguardano la interazione e distribuzione di responsabilità fra classi/oggetti
Livello “scope”
- Classi: Il Pattern riguarda primariamente le relazioni fra classi (e sottoclassi), e quindi tratta aspetti statici (compile-time)
- Oggetti: Il Pattern riguarda primariamente le relazioni fra oggetti (l’esistenza di riferimenti fra oggetti), e quindi tratta aspetti dinamici (run-time)
I 23 Pattern GoF
Creazionali
- A livello di classe: Factory Method
- A livello di oggetto: Abstract Factory, Builder, Prototype, Singleton
Strutturali
- A livello di classe: Adapter
- A livello di oggetto: Adapter, Bridge, Composite, Decorator, Facade, Proxy
Comportamentali
- A livello di classe: Interpreter, Template Method
- A livello di oggetto: Chain of Responsibility, Command, Iterator, Mediator, Memento, Flyweight, Observer, State, Strategy, Visitor
Schema di descrizione per ogni pattern
Aderiremo al seguente schema, che è una semplificazione di quello proposto alla GoF
Ingredienti
- Descrizione in prosa (nome, motivazione, esempi, soluzione)
- Rappresentazione grafica (diagramma delle classi generale)
- Esempio (già visto/nuovo)
Strategy: comportamentale, su oggetti
Intento/motivazione
Definisce una famiglia di algoritmi, e li rende interscambiabili, ossia usabili in modo trasparente dai loro clienti
Esempi
- Strategie di confronto fra due elementi per sorting (
Comparable
)
- Strategia di disposizione di componenti in una GUI (
LayoutManager
)
- Strategie di
map
, filter
, etc.. negli Stream
Soluzione
- Gli algoritmi sono realizzati tramite specializzazioni di una classe/interfaccia base
- Ai clienti passo un oggetto (di una specializzazione) dell’interfaccia base
È probabilmente uno dei pattern più importanti (assieme al Factory Method)
Strategy: UML

Strategy: Sorting con comparatori
public interface PersonCompareStrategy {
int compareTwoPeople(Person p1, Person p2);
}
public class PersonComparatorByAge implements PersonCompareStrategy {
public int compareTwoPeople(Person p1, Person p2) {
return p1.getYearOfBirth() < p2.getYearOfBirth() ? -1 :
(p1.getYearOfBirth() == p2.getYearOfBirth() ? 0 : +1);
}
}
public class PersonComparatorByFullName implements PersonCompareStrategy {
public int compareTwoPeople(Person p1, Person p2) {
String fullName1 = p1.getSurname() + p1.getName();
String fullName2 = p2.getSurname() + p2.getName();
return fullName1.compareTo(fullName2); // reuse compareTo on String
}
}
public class UsePersons {
public static void sortPeople(Person[] ps, PersonCompareStrategy comparator) {
boolean sorted = false;
while(!sorted){ // bubble sort
sorted = true;
for(int i = 0; i < ps.length-1; i++) {
if(comparator.compareTwoPeople(ps[i], ps[i+1]) > 0) {
Person temp = ps[i];
ps[i] = ps[i+1];
ps[i+1] = temp;
sorted = false;
}
}
}
}
public static void printPeople(Person[] people) {
for(int i = 0; i < people.length; i++) {
System.out.println("" + i + ") " + people[i].getName() + " " + people[i].getSurname() +
" - " + people[i].getYearOfBirth());
}
}
public static void main(String[] args){
Person p1 = new Person("Mario", "Rossi", 1985);
Person p2 = new Person("Dario", "Verdi", 1970);
Person p3 = new Person("Zarco", "Neri", 1952);
Person[] people = new Person[] { p1, p2, p3 };
sortPeople(people, new PersonComparatorByAge());
printPeople(people);
System.out.println("\n");
sortPeople(people, new PersonComparatorByFullName());
printPeople(people);
}
}
Analisi
UsePersons
è il contesto della strategia
UsePersons
vuole ordinare un array di Person
, ma astrae dalla strategia impiegata per il confronto
UsePersons
si occupa di fornire il sotto-contesto alla strategia, che in questo caso è dato da una coppia di Person
da confrontare
- Le diverse implementazioni della strategia usano il sotto-contesto diversamente:
PersonComparatorByFullName
si basa su nome/cognome, mentre PersonComparatorByAge
si basa sull’anno di nascita
- Il design è aperto all’introduzione di altre strategie (ad esempio: in base a tratti fisici come l’altezza), ma il contesto rimarrà invariato!