Skip to content
Snippets Groups Projects
Verified Commit 61daecaf authored by Marco Aceti's avatar Marco Aceti
Browse files

Add 'Lezione 07'

parent 610f4638
No related branches found
No related tags found
No related merge requests found
Showing
with 611 additions and 559 deletions
...@@ -7,3 +7,7 @@ resized/ ...@@ -7,3 +7,7 @@ resized/
.vscode/ .vscode/
uml/ uml/
book book
# PlantUML plugin
mdbook-plantuml-img/
.mdbook-plantuml-cache/
This diff is collapsed.
...@@ -7,6 +7,7 @@ title = "Ingegneria del software" ...@@ -7,6 +7,7 @@ title = "Ingegneria del software"
[preprocessor.plantuml] [preprocessor.plantuml]
plantuml-cmd = "plantuml" plantuml-cmd = "plantuml"
clickable-img = true
[output.html] [output.html]
mathjax-support = true mathjax-support = true
# Progettazione
Durante le lezioni, per discutere di progettazione siamo partiti da un esempio di programma in C che stampa una canzone.
Il codice considerato è completamente illegibile:
```c
#include <stdio.h>
main(t,_,a)
char *a;
{
return!0<t?t<3?main(-79,-13,a+main(-87,1-_,main(-86,0,a+1)+a)):
1,t<_?main(t+1,_,a):3,main(-94,-27+t,a)&&t==2?_<13?
main(2,_+1,"%s %d %d\n"):9:16:t<0?t<-72?main(_,t,
"@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#\
;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l \
q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# \
){nl]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw' \
iwk{KK{nl]!/w{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c \
;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# \
}'+}##(!!/")
:t<-50?_==*a?putchar(31[a]):main(-65,_,a+1):main((*a=='/')+t,_,a+1)
:0<t?main(2,2,"%s"):*a=='/'||main(0,main(-61,*a,
"!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:\nuwloca-O;m .vpbks,fxntdCeghiry"),a+1);
}
```
Successivamente abbiamo scomposto il codice per renderlo logicamente più sensato e facilmente modificabile, sono state __estratte le parti comuni__ e spostate in una funzione apposita, mentre le __parti mutabili sono state salvate in alcune strutture dati__; la canzone viene così stampata tramite un ciclo.
In questo modo scrivendo un codice più semplice siamo stati in grado di creare una soluzione più generale e più aperta ai cambiamenti.
```java
public class TwelveDaysOfChristmas {
static String[] days = {"first", "second", ..., "twelfth"};
static String[] gifts = { "a partdrige in a pear tree", "two turtle doves", ... };
static String firstLine(int day) {
return "On the " + days[day] +
" day of Christmas my true love gave to me:\n";
}
static String allGifts(int day) {
if (day == 0) {
return "and " + gifts[0];
} else {
return gifts[day] + "\n" + allGifts(day-1);
}
}
public static void main(String[] args) {
System.out.println(firstLine(0));
System.out.println(gifts[0]);
for (int day == 1; day < 12; day++) {
System.out.println(firstLine(day));
System.out.println(allGifts(day));
}
}
}
```
È importante quindi __adottare la soluzione più semplice__ (che __non è quella più stupida__!) e una misura convenzionale per dire quanto una cosa è semplice - almeno in Università - si esprime in termini del tempo dedicato dal programmatore all'implementazione.
Tale misura si sposa bene con il __TDD__, che richiede __brevi iterazioni__ di circa 10 minuti: se la feature attuale richiede più tempo è opportuno ridurre la portata scomponendo il problema.
- [**Refactoring**](./01_refactoring.md): modifiche al codice senza nuove funzionalità;
- [**Design knowledge**](./02_design-knowledge.md): dove mantenere la conoscenza del design?
- [**Conoscenze preliminari**](./03_conoscenze-preliminari.md): object orientation, SOLID principles, reference escaping, encapsulation e information hiding, immutabilità, code smell
- [**Principio Tell-Don't-Ask**](./04_tell-dont-ask.md)
- [**Interface segregation**](./05_interface-segregation.md)
- [**Esempio**](./06_esempio/00_index.md) applicando i principi precedenti
- [**Analisi del testo naturale**](./07_analisi-testo-naturale.md) con _noun extraction_
# Refactoring
Durante il refactoring è opportuno rispettare le seguenti regole:
- le __modifiche al codice non devono modificare le funzionalità__:
il refactoring DEVE essere invisibile al cliente;
- __non possono essere aggiunti test aggiuntivi__ rispetto alla fase verde appena raggiunta.
Se la fase di refactoring sta richiedendo troppo tempo allora è possibile fare rollback all'ultima versione verde e __pianificare meglio__ l'attività di refactoring, per esempio scomponendolo in più step.
Vale la regola del _"do it twice"_: il secondo approccio a un problema è solitamente più veloce e migliore.
## Motivazioni
Spesso le motivazioni dietro un refactoring sono:
- precedente __design molto complesso e poco leggibile__, a causa della velocità del passare ad uno _scenario verde_;
- __preparare il design di una funzionalità__ che non si integra bene in quello esistente; dopo aver raggiunto uno _scenario verde_ in una feature, è possibile che la feature successiva sia difficile da integrare.
In questo caso, se il _refactoring_ non è banale è bene fermarsi, tornare indietro e evolvere il codice per facilitare l'iterazione successiva (__design for change__).
- presenza di __debito tecnico__ su lavoro fatto in precendenza, ovvero debolezze e "scorciatoie" che ostacolano notevolmente evoluzioni future: _"ogni debito tecnico lo si ripaga con gli interessi"_.
# Design knowledge
La design knowledge è la __conoscenza del design__ architetturale di un progetto.
È possibile utilizzare:
- la __memoria__: non è efficace perché nel tempo si erode, specialmente in coppia;
- i __documenti di design__ (linguaggio naturale o diagrammi): se non viene aggiornato di pari passo con il codice rimane disallineato, risultando più dannoso che d'aiuto.
- le __piattaforme di discussione__ (version control, issue management): possono aiutare ma le informazioni sono sparse in luoghi diversi e di conseguenza difficili da reperire e rimane il problema di mantenere aggiornate queste informazioni.
- gli __UML__: tramite diagrammi UML si è cercato di sfruttare l'approccio ___generative programming___, ovvero la generazione automatica del codice a partire da specificazioni di diagrammi.
Con l'esperienza si è visto che non funziona.
- il __codice__ stesso: tramite la lettura del codice è possibile capire il design ma è difficile rappresentare le ragioni della scelta.
È bene sfruttare tutte le tecniche sopra proposte __combinandole__, partendo dal codice. \
È inoltre importante scrivere documentazione per spiegare le ragioni dietro le scelte effettuate e non le scelte in sé, che si possono dedurre dal codice.
## Condivisione
Per condividere tali scelte di design (il _know how_) è possibile sfruttare:
- __metodi__: con pratiche (come Agile) o addirittura l'object orientation stessa, che può essere un metodo astratto per condividere scelte di design.
- __design pattern__: fondamentali per condividere scelte di design, sono utili anche per generare un vocabolario comune (sfruttiamo dei nomi riconosciuti da tutti per descrivere i ruoli dei componenti) e aiutano l'implementazione (i pattern hanno delle metodologie note per essere implementati).
I pattern non si concentrano sulle prestazioni di un particolare sistema ma sulla generalità e la riusabilità di soluzioni a problemi comuni;
- __principi__: per esempio i principi SOLID.
# Conoscenze preliminari
Prima di proseguire è bene richiamare concetti e termini fondamentali presumibilmente visti durante il corso di Programmazione II.
## Object orientation
Per essere definito _object oriented_, un linguaggio di programmazione deve soddisfare tre proprietà:
- __ereditarietà__: ovvero la possibilità di poter definire una classe ereditando proprietà e comportamenti di un'altra classe.
- __polimorfismo__: quando una classe può assumere diverse forme in base alle interfacce che implementa.
Il prof fa l'esempio del _tennista scacchista_: in un torneo di tennis è poco utile sostituire una persona che gioca a tennis ed è brava con gli scacchi (quindi una classe che implementa entrambe le interfacce) con una che gioca a scacchi.
Il collegamento tra capacità e oggetto è fatto __a tempo di compilazione__: non è importante quindi se la capacità non è ancora definita;
- __collegamento dinamico__: in Java il tipo concreto degli oggetti e quindi il problema di stabilire _quale metodo chiamare_ viene risolto durante l'esecuzione.
In C++ occorre esplicitare questo comportamento utilizzando la keyword `virtual`.
## <a style="color: darkgreen">SOLID</a> principles
Ci sono 5 parti che compongono questo principio:
1. __<span style="color: darkgreen"><big>S</big></span>INGLE RESPONSIBILITY__: una classe, un solo scopo.
Così facendo, le classi rimangono semplici e si agevola la riusabilità.
2. __<span style="color: darkgreen"><big>O</big></span>PEN-CLOSE PRINCIPLE__:
le classi devono essere aperte ai cambiamenti (_opened_) ma senza modificare le parti già consegnate e in produzione (_closed_).
Il refactoring è comunque possibile, ma deve essere preferibile estendere la classe attuale.
3. __<span style="color: darkgreen"><big>L</big></span>ISKOV SUBSTITUTION PRINCIPLE__:
c'è la garanzia che le caratteristiche eredidate dalla classe padre continuinino ad esistere nelle classi figlie.
Questo concetto si collega all'aspetto __contract-based__ del metodo Agile: le _precondizioni_ di un metodo di una classe figlia devono essere ugualmente o meno restrittive del metodo della classe padre.
Al contrario, le _postcondizioni_ di un metodo della classe figlia non possono garantire più di quello che garantiva il metodo nella classe padre.
Fare _casting_ bypassa queste regole.
4. __<span style="color: darkgreen"><big>I</big></span>NTERFACE SEGREGATION__:
più le capacità e competenze di una classe sono frammentate in tante interfacce più è facile utilizzarla in contesti differenti.
In questo modo un client non dipende da metodi che non usa.
Meglio quindi avere __tante interfacce specifiche__ e piccole (composte da pochi metodi), piuttosto che poche, grandi e generali.
5. __<span style="color: darkgreen"><big>D</big></span>EPENDENCY INVERSION__:
il codice dal quale una classe dipende non deve essere più __concreto__ di tale classe.
Per esempio, se il _telaio della FIAT 500_ dipende da uno specifico motore, è possibile utilizzarlo solo per quel specifico motore.
Se invece il telaio dipende da _un_ concetto di motore, non c'è questa limitazione.
In conlusione, le classi concrete devono tendenzialmente dipendere da classi astratte e non da altre classi concrete.
## Reference escaping
Il _reference escaping_ è una violazione dell'incapsulamento.
Può capitare, per esempio:
- quando un getter ritorna un riferimento a un segreto;
```java
public Deck {
private List<Card> cards;
public List<Card> getCards() {
return this.cards;
}
}
```
- quando un setter assegna a un segreto un riferimento che gli viene passato;
```java
public Deck {
private List<Card> cards;
public setCards(List<Card> cards) {
this.cards = cards;
}
}
```
- quando il costruttore assegna al segreto un riferimento che gli viene passato;
```java
public Deck {
private List<Card> cards;
public Deck(List<Card> cards) {
this.cards = cards;
}
}
```
## Encapsulation e information hiding
__Legge di Parnas (L8)__.
> _Solo ciò che è nascosto può essere cambiato liberamente e senza pericoli._
Lo stato mostrato all'esterno non può essere modificato, mentre quello nascosto sì.
Questo principio serve per __facilitare la comprensione del codice__ e renderne più facile la modifica parziale senza fare danni.
## Immutabilità
Una classe è immutabile quando non c'è modo di modificare lo stato di ogni suo oggetto dopo la creazione.
Per assicurare tale proprietà è necessario:
- __non fornire metodi di modifica__ allo stato;
- avere tutti gli __attributi privati__ per i tipi potenzialmente mutabili (come `List<T>`);
- avere tutti gli __attributi final__ se non già privati;
- assicurare l'__accesso esclusivo__ a tutte le parti non mutabili, ovvero non avere reference escaping.
## Code smell
I _code smell_ sono dei segnali che suggeriscono problemi nella progettazione del codice.
Di seguito ne sono elencati alcuni:
- __codice duplicato__: si può fare per arrivare velocemente al verde ma è da togliere con il refactoring.
Le parti di codice in comune possono quindi essere fattorizzate.
- __metodi troppo lunghi__: sono poco leggibili e poco riusabili;
- __troppi livelli di indentazione__: scarsa leggibilità e riusabilità, è bene fattorizzare il codice;
- __troppi attributi__: suggerisce che la classe non rispetta la single responsability, ovvero fa troppe cose;
- __lunghe sequenze di _if-else_ o _switch___;
- __classe troppo grande__;
- __lista parametri troppo lunga__;
- __numeri _magici___: è importante assegnare alle costanti numeriche all'interno del codice un nome per comprendere meglio il loro scopo;
- __commenti che spiegano cosa fa il codice__: indicazione che il codice non è abbastanza chiaro;
- __nomi oscuri o inconsistenti__;
- __codice morto__: nel programma non deve essere presente del codice irraggiungibile o commentato. Utilizzando strumenti di versioning è possibile riaccedere a codice precedentemente scritto con facilità.
- __getter e setter__: vedi principio di __tell don't ask__.
# <a href="https://martinfowler.com/bliki/TellDontAsk.html">Principio Tell-Don't-Ask</a>
![Tell-Don't-Ask](/assets/07_tell-dont-ask.png)
> Non chiedere i dati, ma dì cosa vuoi che si faccia sui dati
Il responsabile di un'informazione è anche responsabile di tutte le operazioni su quell'informazione.
Il principio _Tell-Don't-Ask_ sancisce che piuttosto di __chiedere__ ad un oggetto dei dati e fare delle operazioni con quei dati è meglio __dire__ a questo oggetto cosa fare con i dati che contiene.
## Esempio
Se desideriamo stampare il contenuto di tutte le carte in un mazzo possiamo partire da questo codice.
```java
class Main {
public static void main(String[] args) {
Deck deck = new Deck();
Card card = new Card();
card.setSuit(Suit.DIAMONDS);
card.setRank(Rank.THREE);
deck.getCards().add(card);
deck.getCards().add(new Card()); // <-- !!!
System.out.println("There are " + deck.getCards().size() + " cards:");
for (Card currentCard : deck.getCards()) {
System.out.println(
currentCard.getRank() +
" of " +
currentCard.getSuit()
);
}
}
}
```
All'interno del ciclo reperiamo gli attributi della carta e li utilizziamo per stampare le sue informazioni.
Inoltre, nella riga evidenziata viene aggiunta una carta senza settare i suoi attributi.
La responsabilità della gestione dell'informazione della carta è quindi __erroneamente delegata__ alla classe chiamante.
Per risolvere, è possibile trasformare la classe `Card` nel seguente modo:
```java
class Card {
private Suit suit;
private Rank rank;
public Card(@NotNull Suit s, @NotNull Rank r) {
suit = s;
rank = r;
}
@Override
public String toString() {
return rank + " of " + suit;
}
}
```
l'informazione viene ora interamente gestita dalla classe `Card`, che la gestisce nel metodo `toString()` per ritornare la sua rappresentazione testuale.
# Interface segregation
Le interfacce possono _"nascere"_ tramite due approcci:
- __up front__: scrivere direttamente l'interfaccia;
- __down front__: scrivere il codice e quindi tentare di estrarne un'interfaccia.
L'approccio down-front si adatta meglio al TDD ed è illustrato nel seguente esempio.
# Esempio con gerarchia Card / Deck
In questo esempio sono trattati numerosi principi, come l'_interface segreagation_, _linking dinamico/statico_, _implementazione di interfacce multiple_ e il _contract based design_ vs la _programmazione difensiva_.
- [**Interface segregation**](./01_interface-segregation.md)
- [**Collegamento statico e dinamico**](./02_collegamento-statico-dinamico.md)
- [**Loose coupling**](./03_loose-coupling.md)
- [**Interfacce multiple**](./04_interfacce-multiple.md)
- [**_Contract-based_ design vs programmazione difensiva**](./05_contract-based.md)
- [**Classi astratte**](./06_classi-astratte.md)
# Interface segregation all'opera
```java
public static List<Card> drawCards(Deck deck, int number) {
List<Card> result = new ArrayList<>();
for (int i = 0; i < number && !deck.isEmpty(); i++) {
result.add(deck.draw());
}
return result;
}
```
Consideriamo il metodo `drawCards` che prende come parametri un `Deck` e un intero. \
Le __uniche competenze__ riconosciute a `Deck` sono l'indicazione _se è vuoto_ (`isEmpty()`) e il _pescare una carta_ dal mazzo (`draw()`).
`Deck` può quindi __implementare un'interfaccia__ che mette a disposizione queste capacità.
È possibile modificare il metodo in modo da accettare un qualunque oggetto in grado di eseguire le operazioni sopra elencate, ovvero che implementi l'interfaccia __`CardSource`__.
<a id="cardsource" style="display:none;"></a>
```java
public interface CardSource {
/**
* @return The next available card.
* @pre !isEmpty()
*/
Card draw();
/**
* @return True if there is no card in the source
*/
boolean isEmpty();
}
```
```java
public class Deck implements CardSource { ... }
```
```java
public static List<Card> drawCards(CardSource deck, int number) {
List<Card> result = new ArrayList<>();
for (int i = 0; i < number && !deck.isEmpty(); i++) {
result.add(deck.draw());
}
return result;
}
```
# Collegamento statico e dinamico
Notare come è necessario specificare __staticamente__ che `Deck` implementi `CardSource`, ovvero occorre forzare la dichiarazione del fatto che `Deck` sia un _sottotipo_ di `CardSource` (Java è strong typed) e quindi sia possibile mettere un oggetto `Deck` ovunque sia richiesto un oggetto `CardSource`. \
In altri linguaggi come __Go__ c'è una __maggiore dinamicità__ perché non c'è bisogno di specificare nel codice che un oggetto è sottotipo di qualcos'altro, è sufficiente solo che implementi un metodo con la stessa signature.
Il controllo che l'oggetto passato ad una funzione abbia le capacità necessarie avviene a runtime e non prima.
Un problema della troppa dinamicità (__duck typing__) è che se i metodi di un oggetto non hanno dei nomi abbastanza specifici si possono avere dei problemi.
Per esempio, in un programma per il gioco del tennis se una funzione richiede un oggetto che implementa il metodo `play()`, e riceve in input un oggetto che non c'entra nulla con il tennis (per esempio un oggetto di tipo `GiocatoreDiScacchi`) che ha il metodo `play()`, si possono avere degli effetti indesiderati.
# _Loose coupling_
Il _loose coupling_ è la capacità di una variabile o un parametro di accettare l'assegnamento di oggetti aventi tipo diverso da quello della variabile o parametro, a patto che sia un sottotipo.
```java
Deck deck = new Deck();
CardSource source = deck;
List<Card> cards = drawCards(deck, 5);
```
# Interfacce multiple
Tornando all'esempio, la classe `Deck` (che implementa `CardSource`) __può implementare anche altre interfacce__, come `Shuffable` o `Iterable<Card>`.
Al metodo precedente interessa solo che Deck abbia le capacità specificate in `CardSource`, se poi implementa anche altre interfaccie è ininfluente.
```plantuml
@startuml
scale 1024 width
class Client1
class Client2
class Client3
interface Iterable<Card> << interface >> {
+ {abstract} Iterator<Card> iterator()
}
class Deck implements Iterable, Shuffable, CardSource {
+ void shuffle()
+ Card draw()
+ boolean isEmpty()
+ Iterator<Card> iterator()
}
interface Shuffable << interface >> {
+ {abstract} void shuffle()
}
interface CardSource << interface >> {
+ {abstract} Card draw()
+ {abstract} boolean isEmpty()
}
Client2 ..> Iterable
Client1 ..> Shuffable
Client3 ..> Iterable
Client3 ..> CardSource
hide empty fields
hide empty methods
@enduml
```
\ No newline at end of file
# _Contract-based_ design vs programmazione difensiva
Tornando alla [specificazione](./01_interface-segregation.md#cardsource) dell'interfaccia di `CardSource`, è possibile notare dei commenti in formato Javadoc che specificano le __precondizioni__ e le __postcondizioni__ (il valore di ritorno) del metodo. Secondo il ___contract-based_ design__, esiste un _"contratto"_ tra chi implementa un metodo e chi lo chiama.
Per esempio, considerando il metodo `draw()`, __è responsabilità del chiamante__ verificare il soddisfacimento delle precondizioni (_"il mazzo non è vuoto"_) prima di invocare il metodo.
Se `draw()` viene chiamato quando il mazzo è vuoto ci troviamo in una situazione di __violazione di contratto__ e può anche esplodere la centrale nucleare.
Per specificare il contratto si possono utilizzare delle __asserzioni__ o il `@pre` nei __commenti__.
Le prime sono particolarmenti utili in fase di sviluppo perché interrompono l'esecuzione del programma in caso di violazione, ma vengono solitamente rimosse in favore delle seconde nella fase di deployment.
Un'altro approccio è la __programmazione difensiva__ che al contrario delega la responsabilità del soddisfacimento delle precondizioni al _chiamato_, e non al chiamante.
# Classi astratte
Una classe astratta che implementa un'interfaccia __non deve necessariamente implementarne__ tutti i metodi, ma può delegarne l'implementazione alle sottoclassi impedendo l'istanziamento di oggetti del suo tipo.
Le interfacce diminuiscono leggermente le performance, però migliorano estremamente la generalità (che aiutano l'espandibilità ed evolvibilità del programma), quindi vale la pena di utilizzarle.
È possibile utilizzare le __classi astratte__ anche per classi complete, ma che __non ha senso che siano istanziate__.
Un buon esempio sono le classi _utility_ della libreria standard di Java.
#### Classe utility della libreria standard di Java
Un esempio è __`Collections.shuffle(List<?> list)`__ che accetta una lista omogenea di elementi e la mischia.
Il _tipo_ degli elementi è volutamente ignorato in quanto non è necessario conoscerlo per mischiarli.
Per l'__ordinamento__, invece, è necessario conoscere il tipo degli oggetti in quanto bisogna confrontarli tra loro per poterli ordinare.
La responsabilità della comparazione è però delegata all'oggetto, che deve aderire all'interfaccia `Comparable<T>`.
__`Collections.sort(...)`__ ha, infatti, la seguente signature:
```java
public static <T extends Comparable<? super T>> void sort(List<T> list)
```
La notazione di generico __aggiunge dei vincoli__ su `T`, ovvero il tipo degli elementi contenuti nella lista:
- `T extends Comparable<...>` significa che `T` deve estendere - e quindi implementare - l'interfaccia `Comparable<...>`;
- `Comparable<? super T>` significa che tale interfaccia può essere implementata su un antenato di `T` (o anche `T` stesso).
`Comparable` è un altro esempio di _interface segregation_: serve per specificare che un oggetto ha bisogno della caratteristica di essere comparabile.
__Digressione__: la classe Collections era l'unico modo per definire dei metodi sulle interfacce (es: dare la possibilità di avere dei metodi sulle collezioni, ovvero liste, mappe, ecc), ma ora si possono utilizzare i metodi di default.
# Analisi del testo naturale
Come organizzare la partenza del design suddividendo in classi e responsabilità?
I due approcci principali sono:
- __pattern__: riconoscere una situazione comune da una data;
- __TDD__: partendo dalla soluzione più semplice si definiscono classi solo all'occorrenza.
Un'altra tecnica che vedremo è l'__estrazione dei nomi__ (noun extraction), per un certo senso _naive_ ma adatta in caso di storie complesse.
## Noun extraction
Basandosi sulle specifiche &mdash; come i commenti esplicativi delle User Stories &mdash; si parte dai sostantivi (o frasi sostantivizzate), si _sfolsticono_ con dei criteri, si cercano le relazioni tra loro e quindi si produce la gerarchia delle classi.
Per spiegare il procedimento considereremo il seguente esempio:
> - _The __library__ contains __books__ and __journals__.
> It may have several __copies__ of a given book.
> Some of the books are for __short term loans__ only.
> All other books may be borrowed by any __library member__ for three __weeks__._
> - ___Members of the library__ can normally borrow up to six __items__ at a time, but __members of staff__ may borrow up to 12 items at one time.
> Only member of staff may borrow journals._
> - _The __system__ must keep track of when books and journals are borrowed and returned, enforcing the __rules__ described above._
Nell'esempio sopra sono stati evidenziati i sostantivi e le frasi sostantivizzate.
### Criteri di _sfoltimento_
I criteri di _sfoltimento_ servono per diminuire il numero di sostantivi considerando solo quelli rilevanti per risolvere il problema.
In questa fase, in caso di dubbi è possibile rimandare la decisione a un momento successivo.
Di seguito ne sono riportati alcuni:
- __Ridondanza__: sinonimi, termini diversi per indicare lo stesso concetto. Anche se è stata utilizzata una locuzione diversa potrebbe essere comunque ridondante, sopratutto in lingue diverse dall'inglese in cui ci sono molti sinonimi. \
Nell'esempio: _library member_ e _member of the library_, _loan_ e _short term loan_.
- __Vaghezza__: nomi generici, comuni a qualunque specifica; potrebbero essere sintomo di una _classe comune astratta_. \
Nell'esempio: _items_.
- __Nomi di eventi e operazioni__: nomi che indicano azioni e non hanno un concetto di _stato_. \
Nell'esempio: _loan_.
- __Metalinguaggio__: parti _statiche_ che fanno parte della logica del programma e che quindi non necessitano di essere rappresentati come classi. \
Nell'esempio: _system_, _rules_.
- __Esterne al sistema__: concetti esterni o verità _"assolute"_ al di fuori del controllo del programma. \
Esempio: _library_, _week_ (una settimana ha 7 giorni).
- __Attributi__: informazioni atomiche e primitive (stringhe, interi, ...) relative a una classe, che quindi non necessitano la creazione di una classe di per sé. \
Esempio: _name of the member_ (se ci fosse stato).
Al termine di questa fase, si avrà una lista di classi _"certe"_ e _"incerte"_.
In questo esempio, sono soppravvisuti i termini _journal_, _book_, _copy_ (of _book_), _library member_ e _member of staff_.
### Relazioni tra classi
Il prossimo passo è definire le relazioni tra le classi.
Inizialmente, si collegano con delle _linee_ (non frecce) senza specificare la direzione dell'associazione.
Parliamo di __associazioni__ e non __attributi__ perché non è necessariamente vero che tutte le associazioni si trasformino in attributi.
```plantuml
@startuml
scale 1024 width
class Book
class CopyOfBook
class LibraryMember
class StaffMember
class Journal
Journal -- StaffMember : borrows/returns
Book -- CopyOfBook : is a copy
CopyOfBook -- LibraryMember : borrows/returns
CopyOfBook -- StaffMember : borrows/returns
hide empty fields
hide empty methods
@enduml
```
Il prossimo passo è specificare le __cardinalità__ delle relazioni, come specificato dal linguaggio UML (opposto in questo aspetto al diagramma ER).
La precisione richiesta in questo punto è soggettiva: da una parte, specificare puntualmente il numero massimo di elementi di una associazione può aiutare ad ottimizzare successivamente, dall'altra porta confusione.
```plantuml
@startuml
scale 1024 width
class Book
class CopyOfBook
class LibraryMember
class StaffMember
class Journal
Journal "0..*" -- "0..1" StaffMember : borrows/returns
Book "1" -- "1..*" CopyOfBook : is a copy
CopyOfBook "0..*" -- "0..1" LibraryMember : borrows/returns
CopyOfBook "0..*" -- "0..1" StaffMember : borrows/returns
hide empty fields
hide empty methods
@enduml
```
Dopo aver ragionato sulle cardinalità, si iniziano a cercare __generalizzazioni__ e fattorizzazioni.
In questo caso, notiamo che:
- `StaffMember` _è un_ `LibraryMember` con in più la possibilità di prendere `Journal`.
Inoltre, un altro indicatore è che hanno la stesso tipo di relazioni con gli altri oggetti.
- `Items` è un termine generico per indicare `CopyOfBook` e `Journal`.
```plantuml
@startuml
scale 1024 width
class BorrowableItem
class LibraryMember
class Book
class CopyOfBook extends BorrowableItem
class StaffMember extends LibraryMember
class Journal extends BorrowableItem
Book "1" -- "1..*" CopyOfBook : is a copy
BorrowableItem "0..*" -- "0..1" LibraryMember : borrows/returns
hide empty fields
hide empty methods
@enduml
```
Distinguere `CopyOfBook` e `Journal` è inutile, perché di fatto un `Journal` _è una copia di_ un giornale.
Si può quindi fattorizzare __rimuovendo la generalizzazione__, come mostrato di seguito.
```plantuml
@startuml
scale 1024 width
class Book
class BorrowableItem
class LibraryMember
class StaffMember extends LibraryMember
class Journal
Book "0..1" - "1..*" BorrowableItem : is a copy
BorrowableItem "0..*" -- "0..1" LibraryMember : borrows/returns
BorrowableItem "1..*" - "0..1" Journal : is a copy
hide empty fields
hide empty methods
@enduml
```
È imporante però preoccuparsi delle __cardinalità__ delle relazioni: è sì vero che un `BorrowableItem` può non _essere una copia di_ un `Book` e di un `Journal`, ma deve essere copia di _esattamente_ una delle due opzioni.
UML prevede un __linguaggio OCL__ ([Object Constraint Language](https://en.wikipedia.org/wiki/Object_Constraint_Language)) per esprimere vincoli divesamente impossibili da esprimere in un diagramma.
È anche possibile scrivere il _constraint_ in linguaggio naturale come __nota__.
# Summary # Summary
# 1. Prodotto e processo # Prodotto e processo
- [Ingegneria, qualità e processi](./01_introduzione/00_index.md) - [Ingegneria, qualità e processi](./01_introduzione/00_index.md)
- [Informazioni logistiche](./01_introduzione/01_logistica.md) - [Informazioni logistiche](./01_introduzione/01_logistica.md)
...@@ -56,8 +56,23 @@ ...@@ -56,8 +56,23 @@
- [Build automation](./06_git-workflow/04_strumenti-opensource/01_build-automation.md) - [Build automation](./06_git-workflow/04_strumenti-opensource/01_build-automation.md)
- [Bug tracking](./06_git-workflow/04_strumenti-opensource/02_bug-tracking.md) - [Bug tracking](./06_git-workflow/04_strumenti-opensource/02_bug-tracking.md)
# 2. Progettazione e implementazione # Progettazione e implementazione
# 3. Verifica e convalida - [Progettazione](./07_progettazione/00_index.md)
- [Refactoring](./07_progettazione/01_refactoring.md)
- [Design knowledge](./07_progettazione/02_design-knowledge.md)
- [Conoscenze preliminari](./07_progettazione/03_conoscenze-preliminari.md)
- [Principio Tell-Don't-Ask](./07_progettazione/04_tell-dont-ask.md)
- [Interface segregation](./07_progettazione/05_interface-segregation.md)
- [Esempio](./07_progettazione/06_esempio/00_index.md)
- [Interface segregation all'opera](./07_progettazione/06_esempio/01_interface-segregation.md)
- [Collegamento statico e dinamico](./07_progettazione/06_esempio/02_collegamento-statico-dinamico.md)
- [Loose coupling](./07_progettazione/06_esempio/03_loose-coupling.md)
- [Interfacce multiple](./07_progettazione/06_esempio/04_interfacce-multiple.md)
- [Contract-based design vs programmazione difensiva](./07_progettazione/06_esempio/05_contract-based.md)
- [Classi astratte](./07_progettazione/06_esempio/06_classi-astratte.md)
- [Analisi del testo naturale](./07_progettazione/07_analisi-testo-naturale.md)
# 4. Reti di Petri # Verifica e convalida
# Reti di Petri
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment