Chiarire il principio di responsabilità unica

62

Il principio di responsabilità unica afferma che una classe dovrebbe fare una sola cosa. Alcuni casi sono abbastanza chiari. Gli altri, però, sono difficili perché ciò che sembra "una cosa" se visti ad un dato livello di astrazione possono essere molteplici se visti ad un livello inferiore. Temo inoltre che se il Principio di Responsabilità Unica è onorato ai livelli inferiori, può risultare un codice raviolo eccessivamente disaccoppiato e verboso, in cui vengono spesi più linee creando minuscole classi per tutto e informazioni sull'impianto idraulico piuttosto che risolvere il problema.

Come descriveresti cosa significa "una cosa"? Quali sono alcuni segnali concreti che una classe fa davvero più di "una cosa sola"?

    
posta dsimcha 05.11.2010 - 14:34
fonte

11 risposte

50

Mi piace molto il modo in cui Robert C. Martin (Zio Bob) riafferma il principio della singola responsabilità (collegato al PDF) :

There should never be more than one reason for a class to change

È sottilmente differente dalla tradizionale definizione "dovrebbe fare solo una cosa", e mi piace perché ti costringe a cambiare il modo in cui pensi alla tua classe. Invece di pensare "questo sta facendo una cosa?", Pensi invece a cosa può cambiare e come questi cambiamenti influenzano la tua classe. Ad esempio, se il database cambia, la tua classe deve cambiare? Che cosa succede se il dispositivo di output cambia (ad esempio uno schermo, un dispositivo mobile o una stampante)? Se la tua classe ha bisogno di cambiare a causa di cambiamenti da molte altre direzioni, allora questo è un segnale che la tua classe ha troppe responsabilità.

Nell'articolo collegato, lo zio Bob conclude:

The SRP is one of the simplest of the principles, and one of the hardest to get right. Conjoining responsibilities is something that we do naturally. Finding and separating those responsibilities from one another is much of what software design is really about.

    
risposta data 05.11.2010 - 18:27
fonte
17

Continuo a chiedermi quale problema sta tentando di risolvere SRP? Quando SRP mi aiuta? Ecco cosa mi è venuto in mente:

Dovresti rifattorizzare la responsabilità / funzionalità di una classe quando:

1) Hai duplicato la funzionalità (DRY)

2) Troverai che il tuo codice ha bisogno di un altro livello di astrazione per aiutarti a capirlo (BACIO)

3) Trovi che i tuoi esperti di dominio capiscano che le parti di funzionalità sono distinte da un componente diverso (linguaggio Ubiquitous)

NON DOVRESTI refactoring di una classe quando:

1) Non c'è alcuna funzionalità duplicata.

2) La funzionalità non ha senso al di fuori del contesto della tua classe. In altre parole, la classe fornisce un contesto in cui è più semplice comprendere la funzionalità.

3) Gli esperti di dominio non hanno un'idea di tale responsabilità.

Mi viene in mente che se SRP viene applicato a grandi linee, scambiamo un tipo di complessità (cercando di rendere testa o croce di una classe con troppe cose in corso all'interno) con un altro tipo (cercando di mantenere tutti i collaboratori / livelli di astrazione direttamente per capire cosa fanno effettivamente queste classi).

In caso di dubbio, lascia perdere! Puoi sempre refactoring più tardi, quando c'è un chiaro caso per questo.

Che ne pensi?

    
risposta data 29.12.2010 - 20:26
fonte
4

Non so se c'è una scala oggettiva ma ciò che darebbe questo sarebbe i metodi - non tanto il numero di essi, quanto la varietà della loro funzione. Sono d'accordo che puoi prendere la decomposizione troppo lontano, ma non seguirei regole severe e veloci a riguardo.

    
risposta data 05.11.2010 - 14:39
fonte
4

La risposta sta nella definizione

Ciò che definisce la responsabilità di essere, in definitiva ti dà il limite.

Esempio:

Ho un componente che ha la responsabilità di visualizzare fatture - > in questo caso, se ho iniziato ad aggiungere qualcosa di più, ho infranto il principio.

Se d'altra parte, se dicessi la responsabilità delle gestione delle fatture - > aggiungendo più funzioni più piccole (ad esempio, la stampa di Fattura , l'aggiornamento di Fattura ) sono tutte all'interno di tale limite.

Tuttavia, se quel modulo ha iniziato a gestire qualsiasi funzionalità al di fuori delle fatture , sarebbe fuori da tale limite.

    
risposta data 30.12.2010 - 02:40
fonte
2

Lo vedo sempre su due livelli:

  • Mi assicuro che i miei metodi facciano solo una cosa e facciano bene
  • Vedo una classe come un raggruppamento logico (OO) di quei metodi che rappresenta una cosa bene

Quindi qualcosa come un oggetto di dominio chiamato Dog:

Dog è la mia classe, ma i cani possono fare molte cose! Potrei avere metodi come walk() , run() e bite(DotNetDeveloper spawnOfBill) (sorry non ha potuto resistere; p).

Se Dog diventa ingombrante, penserei a come gruppi di questi metodi possono essere modellati insieme in un'altra classe, come una classe Movement , che potrebbe contenere i miei metodi walk() e run() .

Non esiste una regola ferrea, il tuo design OO si evolverà nel tempo. Cerco di utilizzare un'interfaccia chiara / API pubblica e metodi semplici che facciano bene una cosa e una cosa.

    
risposta data 05.11.2010 - 15:07
fonte
1

Lo guardo più sulla falsariga di una classe dovrebbe solo rappresentare una cosa. Per appropriare l'esempio di @Karianna, ho la mia classe Dog , che ha metodi per walk() , run() e bark() . Non ho intenzione di aggiungere metodi per meaow() , squeak() , slither() o fly() perché quelli non sono cose che fanno i cani. Sono cose che fanno gli altri animali, e quegli altri animali avrebbero le loro classi per rappresentarli.

(A proposito, se il tuo cane fa vola, allora probabilmente dovresti smettere di buttarlo fuori dalla finestra).

    
risposta data 05.11.2010 - 18:57
fonte
1

Una classe dovrebbe fare una cosa se vista al proprio livello di astrazione. Indubbiamente farà molte cose a un livello meno astratto. Ecco come le classi lavorano per rendere i programmi più manutenibili: nascondono i dettagli di implementazione se non hai bisogno di esaminarli da vicino.

Uso i nomi delle classi come test per questo. Se non riesco a dare a una classe un nome descrittivo abbastanza breve, o se quel nome ha una parola come "E", probabilmente sto violando il Principio di Responsabilità Unica.

Nella mia esperienza, è più facile mantenere questo principio ai livelli inferiori, dove le cose sono più concrete.

    
risposta data 29.12.2010 - 21:15
fonte
0

Si tratta di avere un unico ruolo .

Ogni classe dovrebbe essere ripresa da un nome di ruolo. Un ruolo è in effetti un verbo (set di) associato a un contesto.

Ad esempio:

Il file fornisce l'accesso a un file. FileManager gestisce gli oggetti File.

Dati di conservazione delle risorse per una risorsa da un file. ResourceManager conserva e fornisce tutte le risorse.

Qui puoi vedere che alcuni verbi come "gestisci" implicano una serie di altri verbi. I verbi da soli sono meglio pensati come funzioni delle classi, il più delle volte. Se il verbo implica troppe azioni che hanno il loro contesto comune, allora dovrebbe essere una classe in sé.

Quindi, l'idea è solo di farti un'idea semplice di cosa fa la classe definendo un ruolo univoco, che potrebbe essere l'aggregato di diversi sottoprocessi (eseguiti da oggetti membro o altri oggetti).

Spesso creo classi Manager che hanno diverse altre classi in esso. Come una fabbrica, un registro, ecc. Vedi una classe Manager come una specie di capo gruppo, un capo d'orchestra che guida gli altri popoli a lavorare insieme per raggiungere un'idea di alto livello. Ha un ruolo, ma implica lavorare con altri ruoli unici all'interno. Puoi anche vederlo come è organizzata un'organizzazione: un CEO non è produttivo sul puro livello di produttività, ma se non c'è, allora niente può funzionare correttamente insieme. Questo è il suo ruolo.

Quando progetti, identifica ruoli unici. E per ogni ruolo, vedi di nuovo se non può essere tagliato in molti altri ruoli. In questo modo, se hai bisogno di cambiare il modo in cui il tuo Manager costruisce gli oggetti, cambia semplicemente la Fabbrica e vai con la tranquillità in mente.

    
risposta data 05.11.2010 - 16:24
fonte
-1

L'SRP non riguarda solo la divisione delle classi, ma anche la delega della funzionalità.

Nell'esempio di Cane usato sopra, non usare SRP come giustificazione per avere 3 classi separate come DogBarker, DogWalker, ecc. (bassa coesione). Invece, guarda l'implementazione dei metodi di una classe e decidi se "sanno troppo". Puoi ancora avere dog.walk (), ma probabilmente il metodo walk () dovrebbe delegare a un'altra classe i dettagli di come viene eseguita la camminata.

In effetti stiamo permettendo alla classe Dog di avere una ragione per cambiare: perché i Cani cambiano. Ovviamente, combinando questo con altri principi SOLID, estendere Dog per nuove funzionalità piuttosto che cambiare Dog (aperto / chiuso). E tu inseriresti le tue dipendenze come IMove e IEat. E naturalmente si potrebbero creare queste interfacce separate (segregazione dell'interfaccia). Il cane cambierebbe solo se trovassimo un bug o se i Cani fossero fondamentalmente cambiati (Liskov Sub, non estendere e quindi rimuovere il comportamento).

L'effetto netto di SOLID è che si arriva a scrivere nuovo codice più spesso di quanto non si debba modificare il codice esistente, e questa è una grande vittoria.

    
risposta data 18.07.2013 - 00:03
fonte
-1

Tutto dipende dalla definizione di responsabilità e da come questa definizione avrà un impatto sulla manutenzione del tuo codice. Tutto si riduce a una cosa ed è così che il tuo progetto ti aiuterà a mantenere il tuo codice.

E come qualcuno ha detto "Camminare sull'acqua e progettare software da specifiche specifiche è facile, purché entrambi siano congelati".

Quindi, se definiamo la responsabilità in modo più concreto, non è necessario cambiarla.

A volte le responsabilità saranno evidenti, ma a volte potrebbe essere sottile e dobbiamo decidere giudiziosamente.

Supponiamo di aggiungere un'altra responsabilità alla classe Dog chiamata catchThief (). Ora potrebbe portare a un'ulteriore responsabilità diversa. Domani, se il modo in cui il Cane sta catturando il ladro deve essere cambiato dal Dipartimento di Polizia, allora la classe del Cane dovrà essere cambiata. In questo caso sarebbe meglio creare un'altra sottoclasse e chiamarla ThiefCathcerDog. Ma in una prospettiva diversa, se siamo sicuri che non cambierà in nessuna circostanza, o il modo in cui catchThief è stato implementato dipende da qualche parametro esterno, allora è perfettamente ok avere questa responsabilità. Se le responsabilità non sono straordinariamente strane, dobbiamo decidere in modo giudizioso in base al caso d'uso.

    
risposta data 02.08.2013 - 03:34
fonte
-1

"Una ragione per cambiare" dipende da chi sta usando il sistema. Assicurati di aver usato casi per ogni attore e di fare un elenco delle modifiche più probabili, per ogni possibile cambio di caso d'uso assicurati che solo una classe sia influenzata da tale cambiamento. Se stai aggiungendo un caso d'uso completamente nuovo, assicurati di avere solo bisogno di estendere una classe per farlo.

    
risposta data 03.07.2016 - 05:02
fonte

Leggi altre domande sui tag