Principio di responsabilità unica in Java

1. Panoramica

In questo tutorial, discuteremo il principio di responsabilità unica, come uno dei principi SOLID della programmazione orientata agli oggetti.

Nel complesso, andremo in profondità su cos'è questo principio e su come implementarlo durante la progettazione del nostro software. Inoltre, spiegheremo quando questo principio può essere fuorviante.

* SRP = Principio di responsabilità unica

2. Principio di responsabilità unica

Come suggerisce il nome, questo principio afferma che ogni classe dovrebbe avere una responsabilità, un unico scopo . Ciò significa che una classe farà un solo lavoro, il che ci porta a concludere che dovrebbe avere un solo motivo per cambiare .

Non vogliamo oggetti che sappiano troppo e abbiano un comportamento non correlato. Queste classi sono più difficili da mantenere. Ad esempio, se abbiamo una classe che cambiamo molto e per motivi diversi, questa classe dovrebbe essere suddivisa in più classi, ognuna delle quali gestisce una singola preoccupazione. Sicuramente, se si verifica un errore, sarà più facile da trovare.

Consideriamo una classe che contiene codice che modifica in qualche modo il testo. L'unico lavoro di questa classe dovrebbe essere la manipolazione del testo .

public class TextManipulator { private String text; public TextManipulator(String text) { this.text = text; } public String getText() { return text; } public void appendText(String newText) { text = text.concat(newText); } public String findWordAndReplace(String word, String replacementWord) { if (text.contains(word)) { text = text.replace(word, replacementWord); } return text; } public String findWordAndDelete(String word) { if (text.contains(word)) { text = text.replace(word, ""); } return text; } public void printText() { System.out.println(textManipulator.getText()); } }

Sebbene possa sembrare corretto, non è un buon esempio di SRP. Qui abbiamo due responsabilità : manipolare e stampare il testo .

Avere un metodo che stampa il testo in questa classe viola il principio di responsabilità unica. A tal fine, dovremmo creare un'altra classe, che gestirà solo la stampa del testo:

public class TextPrinter { TextManipulator textManipulator; public TextPrinter(TextManipulator textManipulator) { this.textManipulator = textManipulator; } public void printText() { System.out.println(textManipulator.getText()); } public void printOutEachWordOfText() { System.out.println(Arrays.toString(textManipulator.getText().split(" "))); } public void printRangeOfCharacters(int startingIndex, int endIndex) { System.out.println(textManipulator.getText().substring(startingIndex, endIndex)); } }

Ora, in questa classe, possiamo creare metodi per tutte le varianti di stampa del testo che vogliamo, perché questo è il suo lavoro.

3. In che modo questo principio può essere fuorviante?

Il trucco per implementare SRP nel nostro software è conoscere la responsabilità di ogni classe.

Tuttavia, ogni sviluppatore ha la propria visione dello scopo della classe , il che rende le cose complicate. Dal momento che non abbiamo istruzioni rigorose su come implementare questo principio, ci rimangono le nostre interpretazioni di quale sarà la responsabilità.

Ciò significa che a volte solo noi, come progettisti della nostra applicazione, possiamo decidere se qualcosa rientra o meno nell'ambito di una classe.

Quando si scrive una classe secondo il principio SRP, dobbiamo pensare al dominio del problema, alle esigenze aziendali e all'architettura dell'applicazione. È molto soggettivo, il che rende più difficile l'implementazione di questo principio, quindi sembra. Non sarà così semplice come l'esempio che abbiamo in questo tutorial.

Questo ci porta al punto successivo.

4. Coesione

Seguendo il principio SRP, le nostre classi aderiranno a una funzionalità. I loro metodi e dati saranno interessati con uno scopo chiaro. Ciò significa elevata coesione e robustezza, che insieme riducono gli errori .

Quando si progetta un software basato sul principio SRP, la coesione è essenziale, poiché ci aiuta a trovare singole responsabilità per le nostre classi. Questo concetto ci aiuta anche a trovare classi che hanno più di una responsabilità.

Torniamo ai nostri metodi di classe TextManipulator :

... public void appendText(String newText) { text = text.concat(newText); } public String findWordAndReplace(String word, String replacementWord) { if (text.contains(word)) { text = text.replace(word, replacementWord); } return text; } public String findWordAndDelete(String word) { if (text.contains(word)) { text = text.replace(word, ""); } return text; } ...

Qui abbiamo una chiara rappresentazione di ciò che fa questa classe: manipolazione del testo.

Ma, se non pensiamo alla coesione e non abbiamo una definizione chiara di quale sia la responsabilità di questa classe, potremmo dire che scrivere e aggiornare il testo sono due lavori diversi e separati. Guidati da questo pensiero, possiamo concludere che queste dovrebbero essere due classi separate: WriteText e UpdateText .

In realtà, avremmo due classi strettamente collegate e poco coese , che dovrebbero quasi sempre essere usate insieme. Questi tre metodi possono eseguire operazioni diverse, ma servono essenzialmente a un unico scopo : la manipolazione del testo. La chiave è non pensare troppo.

Uno degli strumenti che possono aiutare a raggiungere un'elevata coesione nei metodi è LCOM. In sostanza, LCOM misura la connessione tra i componenti della classe e la loro relazione reciproca.

Martin Hitz e Behzad Montazeri hanno presentato LCOM4, ​​che Sonarqube ha misurato per un po ', ma da allora è stato deprecato.

5. conclusione

Anche se il nome del principio è autoesplicativo, possiamo vedere quanto sia facile implementarlo in modo errato. Assicurati di distinguere le responsabilità di ogni classe quando sviluppi un progetto e presta particolare attenzione alla coesione.

Come sempre, il codice è disponibile su GitHub.