Iniziamo questa lezione introducendo subito un esempio pratico: supponiamo di dover modellare un registratore di cassa: in una prima implementazione potrebbe essere sufficiente esporre due metodi pubblici:
sparaProdotto(codice_a_barre)
- che invoca una routine di calcolo del prezzo e salva il risultato in una variabile di istanza;stampaScontrino()
- che utilizza questa variabile ed invia alla stampante del device il testo dello scontrino, già correttamente formattato, dopodiché svuota la variabile.
Ora proviamo a chiederci: quante tipologie di cambiamento nel business del cliente insistono necessariamente su di una modifica di questa classe? Eccone alcune:
- cambia il formato dello scontrino;
- cambia il device su cui lo scontrino viene stampato;
- cambia un qualsiasi aspetto della procedura di calcolo del prezzo;
- cambia il formato del codice a barre (magari si utilizzeranno gli RFID);
Il Single Responsibility Principle afferma che:
“ Ogni oggetto deve essere responsabile di un singolo aspetto del comportamento che stiamo modellando.”
La giustificazione di questa regola è assolutamente lampante se si formulano alcune osservazioni sull'esempio precedente:
- come riutilizzare il metodo
calcolaPrezzo
anche per la parte di contabilità di questo software? - come riutilizzare il metodo
sparaProdotto
anche in una applicazione di magazzino?
In entrambi i casi infatti affidarci a RegistratoreDiCassa
anche per questi due utilizzi sarebbe inopportuno in quanto quello che veramente stiamo cercando sono soltanto alcuni aspetti, o responsabilità di questa classe e, di conseguenza, l'eventuale evoluzioni di queste caratteristiche non deve comportare la modifica di altre.
Individuare le responsabilità
Facciamo un po' d'ordine e cerchiamo di individuare le responsabilità che emergono nello scenario della gestione del registratore di cassa:
- Trasformazione dal codice a barre al codice di prodotto;
- Calcolo del prezzo del prodotto in base a varie regole e scontistiche;
- Generazione del documento di stampa dello scontrino;
- Stampa dello scontrino su apposito device.
Ora identifichiamo gli oggetti ai quali dare in appalto queste responsabilità: un parser che riconosca il codice a barre, un oggetto che calcoli i prezzi e uno che generi scontrini.
Avendo separato in modo così netto le responsabilità fra questi tre oggetti, possiamo beneficiare
di due importanti vantaggi nella stesura del codice restante:
- Ognuna di queste classi sarà molto semplice, e dovrà essere modificata solamente nel caso ci
sia un effettivo cambio funzionale nell'aspetto per il quale la classe è responsabile; - Ognuna di queste classi potrà essere utilizzata tranquillamente in modo separato dalle altre.
Ecco infine un diagramma che riassume l'operatività delle classi che abbiamo appena definito
all'interno del contesto del progetto:
Conclusioni
Come lo stesso Robert C. Martin afferma, il Single Responsibility Principle è abbastanza semplice nella sua comprensione e difficilissimo nella sua messa in pratica: come si identificano in modo oggettivo le responsabilità di un dato oggetto?
Purtroppo una procedura univoca non esiste e bisogna fare ancora largo affidamento sulla percezione delle operatività funzionali del progetto e beneficiare della propria esperienza sul campo.