Salta al contenuto
Note per Studenti Esercizio - assegnare le responsabilità con GRASP alla restituzione di un prestito (progetto originale)

Esercizio - assegnare le responsabilità con GRASP alla restituzione di un prestito (progetto originale)

Esercizio originale, nello stile del progetto d'esame (assegnazione delle responsabilità con GRASP, con il codice). Teoria: Principi GRASP per l'assegnazione delle responsabilitàGRASP (General Responsibility Assignment Software Patterns) sono principi per assegnare le responsabilità alle classi, dove una responsabilità è un obbligo di conoscere (knowing) o di fare (doing): Creator (chi crea A? la classe che contiene o registra A, la usa da vicino o ha i dati per inizializzarla), Information Expert (la classe che ha le informazioni necessarie), Low Coupling (poche dipendenze tra classi per ridurre l'impatto dei cambiamenti), Controller (chi riceve gli eventi di sistema? un oggetto che rappresenta il sistema o il caso d'uso, che coordina e non calcola), High Cohesion (classi focalizzate su un unico scopo); si completano con Polymorphism, Pure Fabrication, Indirection e Protected Variations, che sono alla base di molti pattern GoF.Principi GRASP per l'assegnazione delle responsabilità →. Il codice è stato compilato ed eseguito con JDK 21.

Testo

Caso d'uso Registrare una restituzione. Quando il bibliotecario registra la restituzione di un prestito, il sistema deve:

  1. togliere il prestito dai prestiti attivi;
  2. calcolare i giorni di ritardo rispetto alla scadenza;
  3. se c'è ritardo, addebitare la multa all'utente (tariffa giornaliera 0,10 euro per i soci, 0,20 euro per gli altri);
  4. rimettere la copia in scaffale e, se qualcuno aveva prenotato il documento, individuare chi avvisare.

Domanda di progetto: partendo dalla sola operazione di sistema registraRestituzione(prestito, data), a quale classe va assegnata ciascuna responsabilità? Si richiede di: 1) elencare le responsabilità (di sapere e di fare); 2) assegnarle indicando per ciascuna il principio GRASP; 3) scrivere il codice Java con un main (anna, socia, restituisce con 13 giorni di ritardo; luca ha prenotato); 4) discutere una alternativa scartata e verificare accoppiamento e coesione.

Soluzione

1. Responsabilità

# responsabilità tipo
1 ricevere l'operazione di sistema e coordinarla fare (coordinare)
2 togliere il prestito dai prestiti attivi fare
3 sapere i giorni di ritardo sapere
4 sapere la tariffa giornaliera di un utente sapere
5 addebitare un importo all'utente fare
6 rimettere in scaffale la copia fare
7 sapere chi ha prenotato e darne il primo sapere/fare
8 creare il prestito (all'apertura) fare (creare)

2. Assegnazione

# classe principio motivo
1 Biblioteca Controller (di caso d'uso, coordina) è il primo oggetto oltre l'interfaccia, rappresenta il sistema; non contiene formule, delega
2 Biblioteca Information Expert tiene la collezione dei prestiti attivi
3 Prestito Information Expert ha la scadenza, quindi sa confrontarla con la data di restituzione
4 Utente Information Expert sa se è socio
5 Utente Information Expert tiene le multe dovute
6, 7 Documento Information Expert ha le copie e la coda delle prenotazioni
8 Biblioteca Creator registra i prestiti (e ha i dati: utente, documento, scadenza)

Il calcolo della multa è una collaborazione: Biblioteca chiede a Prestito il ritardo, a Utente la tariffa e l'addebito; nessuna classe conosce i dettagli delle altre (Low Coupling), e ogni classe ha responsabilità affini ai propri dati (High Cohesion).

3. Codice

java
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

/** Information Expert: sa quanti giorni di ritardo ha un prestito perche' conosce scadenza e data di restituzione. */
class Prestito {
    private final Documento documento;
    private final Utente utente;
    private final LocalDate scadenza;
    Prestito(Documento documento, Utente utente, LocalDate scadenza) {
        this.documento = documento; this.utente = utente; this.scadenza = scadenza;
    }
    Documento documento() { return documento; }
    Utente utente() { return utente; }
    int giorniRitardo(LocalDate oggi) { return (int) Math.max(0, ChronoUnit.DAYS.between(scadenza, oggi)); }
}

/** Information Expert: la politica di multa dipende dal tipo di utente, che e' l'utente a conoscere. */
class Utente {
    private final String nome;
    private final boolean socio;
    private double multeDovute = 0;
    Utente(String nome, boolean socio) { this.nome = nome; this.socio = socio; }
    String nome() { return nome; }
    double tariffaGiornaliera() { return socio ? 0.10 : 0.20; }
    void addebita(double importo) { multeDovute += importo; }
    double multeDovute() { return multeDovute; }
}

/** Information Expert: la coda di prenotazione e le copie sono del documento. */
class Documento {
    private final String titolo;
    private int copie;
    private final Queue<Utente> prenotazioni = new LinkedList<>();
    Documento(String titolo, int copie) { this.titolo = titolo; this.copie = copie; }
    String titolo() { return titolo; }
    void prenota(Utente u) { prenotazioni.add(u); }
    /** Rimette in scaffale la copia; restituisce chi deve essere avvisato (o null). */
    Utente rientra() { copie++; return prenotazioni.poll(); }
    int copie() { return copie; }
}

/**
 * Controller di caso d'uso: riceve l'operazione di sistema "registraRestituzione" e coordina,
 * senza contenere ne' la formula della multa ne' la gestione delle prenotazioni.
 */
class Biblioteca {
    private final List<Prestito> prestitiAttivi = new ArrayList<>();
    private final Map<String, String> avvisiInviati = new HashMap<>();

    Prestito apri(Documento d, Utente u, LocalDate scadenza) {           // Creator: Biblioteca registra i prestiti
        Prestito p = new Prestito(d, u, scadenza);
        prestitiAttivi.add(p);
        return p;
    }

    void registraRestituzione(Prestito p, LocalDate oggi) {
        prestitiAttivi.remove(p);
        int ritardo = p.giorniRitardo(oggi);                              // chi sa, fa
        if (ritardo > 0) p.utente().addebita(ritardo * p.utente().tariffaGiornaliera());
        Utente prossimo = p.documento().rientra();
        if (prossimo != null) avvisiInviati.put(prossimo.nome(), p.documento().titolo());
    }

    Map<String, String> avvisi() { return avvisiInviati; }
}

public class Restituzione {
    public static void main(String[] args) {
        Biblioteca b = new Biblioteca();
        Utente anna = new Utente("anna", true), luca = new Utente("luca", false);
        Documento rosa = new Documento("Il nome della rosa", 0);
        rosa.prenota(luca);
        Prestito p = b.apri(rosa, anna, LocalDate.of(2026, 10, 1));
        b.registraRestituzione(p, LocalDate.of(2026, 10, 14));            // 13 giorni di ritardo, socia
        System.out.println("multa di anna: " + anna.multeDovute());
        System.out.println("avvisi: " + b.avvisi());
        System.out.println("copie: " + rosa.copie());
    }
}

Uscita:

multa di anna: 1.3
avvisi: {luca=Il nome della rosa}
copie: 1

Con 13 giorni di ritardo e tariffa da socia (0,10 euro) la multa è 1,30 euro; la copia torna in scaffale (copie: 1) e l'avviso va a luca, che aveva prenotato.

4. Alternativa scartata e verifica

Alternativa: mettere tutto in Biblioteca.registraRestituzione (ritardo, tariffa, multa, coda). Il metodo dovrebbe leggere la scadenza dal prestito, sapere come si distinguono i soci, manipolare il saldo dell'utente e la coda del documento: Biblioteca dipenderebbe dalla struttura interna di quattro classi (alto accoppiamento) e conterrebbe regole di quattro domini diversi (bassa coesione, un controller "gonfio"). Cambiare la tariffa dei soci o la politica delle prenotazioni richiederebbe di modificare Biblioteca.

Verifica sul progetto scelto.

classe classi che conosce responsabilità
Biblioteca Prestito, Utente, Documento (solo le interfacce pubbliche) coordinare, creare i prestiti, tenere i prestiti attivi
Prestito Documento, Utente scadenza e ritardo
Utente (nessuna) tariffa e multe
Documento Utente (nella coda) copie e prenotazioni

Le regole che cambiano più spesso (tariffe, ritardi) stanno in classi piccole e coese; Biblioteca resta stabile. Un'ulteriore evoluzione (strategie di multa diverse) si realizza con Polymorphism/Strategy dentro Utente (Pattern comportamentali - Observer, Strategy, Command, Iterator e Template MethodI pattern comportamentali descrivono come gli oggetti collaborano e si dividono le responsabilità: Observer (publish-subscribe) avvisa automaticamente i dipendenti quando il soggetto cambia stato, mantenendo basso l'accoppiamento (si registrano e cancellano a runtime, modello push o pull, attenzione alle modifiche durante la notifica); Strategy rende intercambiabili gli algoritmi dietro un'interfaccia (in Java anche con una lambda); Command trasforma la richiesta in un oggetto, quindi memorizzabile, accodabile e annullabile (undo); Iterator scorre un aggregato senza esporne la struttura (for-each, fail-fast); Template Method fissa lo scheletro di un algoritmo e lascia alle sottoclassi passi astratti o hook (principio di Hollywood); State, Visitor, Chain of Responsibility, Mediator, Memento e Interpreter completano le undici forme del catalogo.Pattern comportamentali - Observer, Strategy, Command, Iterator e Template Method →), senza toccare il controller.

Versione ripasso

  • Operazione di sistema: registraRestituzione(prestito, data); responsabilità: coordinare, togliere dai prestiti attivi, sapere il ritardo, sapere la tariffa, addebitare la multa, rimettere la copia, trovare chi ha prenotato.
responsabilità classe principio
ricevere e coordinare Biblioteca Controller
prestiti attivi Biblioteca Expert
giorni di ritardo Prestito Expert (ha la scadenza)
tariffa giornaliera, multe dovute Utente Expert
copie e coda delle prenotazioni Documento Expert
creare i Prestito Biblioteca Creator (li registra)
java
void registraRestituzione(Prestito p, LocalDate oggi) {          // Controller: coordina e delega
    prestitiAttivi.remove(p);
    int ritardo = p.giorniRitardo(oggi);                          // chi sa, fa
    if (ritardo > 0) p.utente().addebita(ritardo * p.utente().tariffaGiornaliera());
    Utente prossimo = p.documento().rientra();                    // copia in scaffale, primo prenotato
    if (prossimo != null) avvisiInviati.put(prossimo.nome(), p.documento().titolo());
}
  • Uscita: multa di anna: 1.3 (13 giorni a 0,10), avvisi: {luca=Il nome della rosa}, copie: 1.
  • Alternativa scartata: tutto in Biblioteca: alto accoppiamento (conosce la struttura di quattro classi), bassa coesione, controller gonfio; ogni cambio di tariffa o di prenotazioni lo tocca.
  • Verifica: Biblioteca conosce solo le interfacce pubbliche; Utente non conosce nessuno; le regole che cambiano (tariffe) stanno in classi piccole; evoluzione con Strategy dentro Utente (Polymorphism) senza toccare il controller.
  • Collaborazione per la multa: Biblioteca chiede a Prestito il ritardo e a Utente tariffa e addebito; nessuna classe conosce i dettagli delle altre (Low Coupling), ogni classe tiene responsabilità affini ai propri dati (High Cohesion).
  • Tabella di verifica: Biblioteca conosce Prestito, Utente, Documento (solo interfacce pubbliche); Prestito conosce Documento e Utente; Utente nessuna; Documento conosce Utente solo nella coda.
  • Errori tipici: mettere formule nel controller; far calcolare il ritardo a chi non ha la scadenza; classi che sono solo contenitori di dati; non giustificare ogni assegnazione con il principio.

Teoria collegata