Quando si utilizza il principio di responsabilità unica, cosa costituisce una "responsabilità?"

187

Sembra abbastanza chiaro che "Principio di singola responsabilità" non significa "fa solo una cosa". Ecco a cosa servono i metodi.

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}

Bob Martin dice che "le classi dovrebbero avere solo una ragione per cambiare". Ma questo è difficile da racimolare se sei un programmatore nuovo di SOLID.

Ho scritto una risposta ad un'altra domanda , dove ho suggerito che le responsabilità sono come titoli di lavoro, e ballato in giro l'argomento usando una metafora del ristorante per illustrare il mio punto. Ma questo non articola ancora una serie di principi che qualcuno potrebbe usare per definire le responsabilità delle loro classi.

Quindi come lo fai? Come si determinano le responsabilità che ogni classe dovrebbe avere e come si definisce una responsabilità nel contesto dell'SRP?

    
posta Robert Harvey 27.03.2017 - 21:32
fonte

16 risposte

109

Un modo per capirlo è immaginare possibili cambiamenti nei requisiti nei progetti futuri e chiedersi cosa dovrai fare per farli accadere.

Ad esempio:

New business requirement: Users located in California get a special discount.

Example of "good" change: I need to modify code in a class that computes discounts.

Example of bad changes: I need to modify code in the User class, and that change will have a cascading effect on other classes that use the User class, including classes that have nothing to do with discounts, e.g. enrollment, enumeration, and management.

o

New nonfunctional requirement: We'll start using Oracle instead of SQL Server

Example of good change: Just need to modify a single class in the data access layer that determines how to persist the data in the DTOs.

Bad change: I need to modify all of my business layer classes because they contain SQL Server-specific logic.

L'idea è di minimizzare l'impronta dei futuri cambiamenti potenziali, limitando le modifiche del codice a un'area del codice per area di cambiamento.

Al minimo, le tue classi dovrebbero separare le preoccupazioni logiche dai problemi fisici. Un grande insieme di esempi può essere trovato nel namespace System.IO : lì possiamo trovare vari tipi di flussi fisici (ad esempio FileStream , MemoryStream o NetworkStream ) e vari lettori e scrittori ( BinaryWriter , TextWriter ) che funzionano a livello logico. Separandoli in questo modo, evitiamo un'esplosione combinatoria: invece di aver bisogno di FileStreamTextWriter , FileStreamBinaryWriter , NetworkStreamTextWriter , NetworkStreamBinaryWriter , MemoryStreamTextWriter e MemoryStreamBinaryWriter , devi solo collegare lo scrittore e lo stream e tu può avere quello che vuoi Successivamente, possiamo aggiungere, ad esempio, un XmlWriter , senza doverlo implementare nuovamente per memoria, file e rete separatamente.

    
risposta data 27.03.2017 - 21:51
fonte
73

In pratica, le responsabilità sono limitate da quelle che probabilmente devono cambiare. Quindi, non esiste un modo scientifico o formulato per arrivare a ciò che costituisce una responsabilità, sfortunatamente. È un giudizio.

Riguarda cosa, nella tua esperienza , è probabile che cambi.

Tendiamo ad applicare il linguaggio del principio in una rabbia iperbolica, letterale, zelante. Tendiamo a dividere le classi perché potrebbero cambiare, o lungo le linee che semplicemente ci aiutano a risolvere i problemi. (Quest'ultima ragione non è intrinsecamente cattiva.) Ma l'SRP non esiste per il suo stesso interesse; è al servizio della creazione di software gestibile

Quindi di nuovo, se le divisioni non sono guidate da modifiche probabilmente , non sono veramente in servizio per lo SRP 1 se YAGNI è più applicabile. Entrambi servono lo stesso obiettivo finale. E entrambi sono questioni di giudizio - si spera che il seasoned giudizio.

Quando lo zio Bob scrive su questo, suggerisce che pensiamo a "responsabilità" in termini di "chi sta chiedendo il cambiamento". In altre parole, non vogliamo che la Parte A perda il lavoro perché la Parte B ha chiesto una modifica.

When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function. You want to isolate your modules from the complexities of the organization as a whole, and design your systems such that each module is responsible (responds to) the needs of just that one business function. (Uncle Bob - The Single Responsibility Principle )

Gli sviluppatori buoni ed esperti avranno un senso per cui sono probabili cambiamenti. E quella lista mentale varierà un po 'per industria e organizzazione.

Ciò che costituisce una responsabilità nella tua particolare applicazione, nella tua particolare organizzazione, è in definitiva una questione di giudizio stagionato . Riguarda ciò che è probabile che cambi. E, in un certo senso, riguarda chi possiede la logica interna del modulo.

1. Per essere chiari, ciò non significa che siano divisioni cattive . Potrebbero essere grandi divisioni che migliorano notevolmente la leggibilità del codice. Significa semplicemente che non sono guidati dall'SRP.

    
risposta data 27.03.2017 - 23:00
fonte
27

I follow "Le classi dovrebbero avere solo una ragione per cambiare".

Per me, questo significa pensare a schemi scorretti che il proprietario del mio prodotto potrebbe inventare ("Dobbiamo supportare i dispositivi mobili!", "Dobbiamo andare sul cloud!", "Dobbiamo supportare i cinesi!") . I buoni progetti limiteranno l'impatto di questi schemi su aree più piccole e li renderanno relativamente facili da realizzare. Disegni cattivi significano andare a un sacco di codice e fare un sacco di cambiamenti rischiosi.

L'esperienza è l'unica cosa che ho trovato per valutare correttamente la probabilità di quegli schemi folli - perché rendere facile uno potrebbe rendere più difficili altri due - e valutare la bontà di un progetto. I programmatori esperti possono immaginare che cosa dovrebbero fare per cambiare il codice, cosa giace in giro per morderli nel culo e quali trucchi semplificano le cose. I programmatori esperti hanno un buon istinto per quanto sono avvitati quando il proprietario del prodotto chiede cose pazzesche.

Praticamente, trovo che i test unitari aiutano qui. Se il tuo codice non è flessibile, sarà difficile da testare. Se non riesci a iniettare mock o altri dati di test, probabilmente non sarai in grado di iniettare quel codice SupportChinese .

Un'altra metrica approssimativa è il passo dell'elevatore. Le piazzole tradizionali per ascensori sono "se fossi in un ascensore con un investitore, puoi venderlo per un'idea?". Le startup devono avere una descrizione semplice e breve di ciò che stanno facendo - qual è il loro obiettivo. Allo stesso modo, le classi (e le funzioni) dovrebbero avere una semplice descrizione di ciò che fanno . Non "questa classe implementa alcuni fubar tali da poter essere utilizzati in questi specifici scenari". Qualcosa che puoi dire a un altro sviluppatore: "Questa classe crea utenti". Se non puoi comunicarlo ad altri sviluppatori, stai andando per ottenere bug.

    
risposta data 27.03.2017 - 22:23
fonte
23

Nessuno lo sa. O almeno, non siamo in grado di concordare una definizione. Questo è ciò che rende SPR (e altri principi SOLIDI) piuttosto controverso.

Direi che essere in grado di capire che cosa sia o meno una responsabilità è una delle competenze che lo sviluppatore del software deve imparare nel corso della sua carriera. Più codice scrivi e rivedi, più esperienza dovrai determinare se qualcosa è responsabilità singola o multipla. O se la singola responsabilità è fratturata attraverso parti separate del codice.

Direi che lo scopo principale di SRP non è quello di essere una regola difficile. È per ricordarci di essere consapevoli della coesione nel codice e di dedicare sempre uno sforzo consapevole nel determinare quale codice è coeso e cosa no.

    
risposta data 27.03.2017 - 22:24
fonte
5

Penso che il termine "responsabilità" sia utile come metafora perché ci consente di utilizzare il software per indagare sul modo in cui è organizzato il software. In particolare, mi concentrerei su due principi:

  • La responsabilità è commisurata all'autorità.
  • Nessuna entità dovrebbe essere responsabile della stessa cosa.

Questi due principi ci permettono di scaricare la responsabilità in modo significativo perché si giocano a vicenda. Se si sta abilitando un pezzo di codice per fare qualcosa per te, deve avere una responsabilità per quello che fa. Ciò causa la responsabilità che una classe potrebbe dover crescere, espandendo la sua "una ragione per cambiare" a scopi più ampi e più ampi. Tuttavia, man mano che ingrandisci le cose, inizi naturalmente ad imbatterti in situazioni in cui più entità sono responsabili della stessa cosa. Questo è pieno di problemi nella responsabilità della vita reale, quindi sicuramente è un problema anche nella codifica. Di conseguenza, questo principio riduce gli ambiti, mentre suddividi la responsabilità in pacchi non duplicati.

Oltre a questi due, un terzo principio sembra ragionevole:

  • La responsabilità può essere delegata

Considera un programma appena coniato ... una lavagna vuota. All'inizio, hai solo un'entità, che è il programma nel suo insieme. È responsabile di ... tutto. Naturalmente ad un certo punto inizierai a delegare responsabilità a funzioni o classi. A questo punto, entrano in gioco le prime due regole che ti obbligano a bilanciare questa responsabilità. Il programma di livello superiore è ancora responsabile per l'output complessivo, proprio come un manager è responsabile della produttività della propria squadra, ma a ciascuna sub-entità è stata delegata la responsabilità, e con essa l'autorità di svolgere tale responsabilità.

Come ulteriore vantaggio, questo rende SOLID particolarmente compatibile con qualsiasi sviluppo di software aziendale che potrebbe essere necessario fare. Ogni azienda sul pianeta ha qualche idea su come delegare la responsabilità, e non tutti sono d'accordo. Se deleghi la responsabilità all'interno del tuo software in un modo che ricorda la delega della tua azienda, sarà molto più facile per gli sviluppatori futuri venire aggiornati su come fai le cose in questa azienda.

    
risposta data 28.03.2017 - 23:17
fonte
5

In questa conferenza a Yale, Uncle Bob fornisce questo divertente esempio:

DicecheEmployeehatremotivipercambiare,trefontidirequisitidimodificaeforniscequestaumoristicaeironica,macomunqueillustrativa,spiegazione:

  • IftheCalcPay()methodhasanerrorandcoststhecompanymillionsofUS$,theCFOwillfireyou.

  • IftheReportHours()methodhasanerrorandcoststhecompanymillionsofUS$,theCOOwillfireyou.

  • IftheWriteEmmployee()methodhasanerrorthatcausestheerasureofalotofdataandcoststhecompanymillionsofUS$,theCTOwillfireyou.

SohavingthreedifferentClevelexecspotentiallyfiringyouforcostlyerrorsinthethesameclassmeanstheclasshastoomanyresponsibilities.

ForniscequestasoluzionecherisolvelaviolazionediSRP,madeveancorarisolverelaviolazionediDIPchenonèmostratanelvideo.

    
risposta data 29.03.2017 - 01:47
fonte
3

Penso che un modo migliore di suddividere le cose rispetto a "ragioni per cambiare" sia iniziare pensando se avrebbe senso richiedere che il codice che deve eseguire due (o più) azioni debba tenere un riferimento all'oggetto separato per ogni azione e se sarebbe utile avere un oggetto pubblico che potrebbe eseguire un'azione ma non l'altra.

Se le risposte a entrambe le domande sono sì, ciò suggerirebbe che le azioni dovessero essere svolte da classi separate. Se le risposte a entrambe le domande sono no, ciò suggerirebbe che da un punto di vista pubblico dovrebbe esserci una classe; se il codice per questo sarebbe ingombrante, potrebbe essere suddiviso internamente in classi private. Se la risposta alla prima domanda è no, ma il secondo è sì, dovrebbe esserci una classe separata per ogni azione più una classe composta che include riferimenti a istanze degli altri.

Se uno ha classi separate per la tastiera di un registratore di cassa, segnale acustico, lettura numerica, stampante per ricevute e cassetto contanti e nessuna classe composita per un registratore di cassa completo, il codice che dovrebbe elaborare una transazione potrebbe finire per ottenerlo accidentalmente invocato in un modo che prende input dalla tastiera di una macchina, produce rumore dal cicalino di una seconda macchina, mostra i numeri sul display di una terza macchina, stampa una ricevuta sulla stampante di una quarta macchina e fa scattare un cassetto della quinta macchina. Ognuna di queste funzioni secondarie potrebbe essere gestita utilmente da una classe separata, ma dovrebbe esserci anche una classe composta che le unisce. La classe composita dovrebbe delegare la maggior quantità possibile di logica alle classi costituenti, ma dovrebbe essere utile quando si eseguono funzioni di riepilogo delle componenti costitutive piuttosto che richiedere al codice client l'accesso diretto ai componenti.

Si potrebbe dire che la "responsabilità" di ogni classe è di incorporare qualche logica reale o di fornire un punto di collegamento comune per più altre classi che lo fanno, ma ciò che è importante è focalizzarsi in primo luogo su come il codice del client dovrebbe visualizzare una classe. Se ha senso che il codice client visualizzi qualcosa come un singolo oggetto, il codice client dovrebbe vederlo come un singolo oggetto.

    
risposta data 28.03.2017 - 01:01
fonte
3

SRP è difficile da ottenere. Si tratta principalmente di assegnare "lavori" al codice e assicurarsi che ogni parte abbia responsabilità chiare. Come nella vita reale, in alcuni casi suddividere il lavoro tra le persone può essere del tutto naturale, ma in altri casi può essere davvero complicato, specialmente se non li conosci (o il lavoro).

Ti consiglio sempre di scrivere un codice semplice che funzioni per primo , poi di rifattorizzare un po ': tenderai a vedere come i cluster di codice naturalmente dopo un po'. Penso che sia un errore forzare le responsabilità prima che tu conosca il codice (o le persone) e il lavoro da svolgere.

Una cosa che noterete è quando il modulo inizia a fare troppo ed è difficile eseguirne il debug / manutenzione. Questo è il momento di refactoring; quale dovrebbe essere il lavoro principale e quali compiti potrebbero essere assegnati a un altro modulo? Ad esempio, se dovesse gestire i controlli di sicurezza e l'altro lavoro, o prima dovresti fare controlli di sicurezza altrove, o questo renderà il codice più complesso?

Usa troppe riferimenti indiretti e diventa di nuovo un disastro ... come per altri principi, questo sarà in conflitto con altri, come KISS, YAGNI, ecc. Tutto è una questione di equilibrio.

    
risposta data 28.03.2017 - 10:35
fonte
3

"Principio di responsabilità unica" è forse un nome confuso. "Un solo motivo per cambiare" è una migliore descrizione del principio, ma è ancora facile fraintendere. Non stiamo parlando di dire cosa fa cambiare lo stato degli oggetti in fase di runtime. Stiamo prendendo in considerazione ciò che potrebbe far sì che gli sviluppatori debbano cambiare il codice in futuro.

A meno che non stiamo correggendo un bug, la modifica sarà dovuta a un requisito aziendale nuovo o modificato. Dovrai pensare al di fuori del codice stesso, e immaginare quali fattori esterni potrebbero causare requisiti per cambiare indipendentemente . Di ':

  • Le aliquote fiscali cambiano a causa di una decisione politica.
  • Il marketing decide di cambiare i nomi di tutti i prodotti
  • L'interfaccia utente deve essere ridisegnata per essere accessibile
  • Il database è congestionato, quindi è necessario fare alcune ottimizzazioni
  • Devi ospitare un'app mobile
  • e così via ...

Idealmente vuoi che i fattori indipendenti influiscano su classi diverse. Per esempio. poiché le aliquote d'imposta cambiano indipendentemente dai nomi dei prodotti, le modifiche non dovrebbero influire sulle stesse classi. Altrimenti si corre il rischio che l'introduzione di una modifica delle tasse comporti un errore nella denominazione del prodotto, che è il tipo di accoppiamento stretto che si desidera evitare con un sistema modulare.

Quindi non concentrarti solo su ciò che potrebbe cambiare: qualsiasi cosa potrebbe cambiare in futuro. Concentrati su cosa potrebbe cambiare in modo indipendente . Le modifiche sono in genere indipendenti se sono causate da attori diversi.

Il tuo esempio con titoli di lavoro è sulla strada giusta, ma dovresti prenderlo più alla lettera! Se il marketing potrebbe causare modifiche al codice e la finanza potrebbe causare altre modifiche, queste modifiche non dovrebbero influire sullo stesso codice, poiché si tratta di titoli di lavoro letteralmente diversi e pertanto le modifiche avverranno in modo indipendente.

Per citare lo zio Bob che ha inventato il termine:

When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function. You want to isolate your modules from the complexities of the organization as a whole, and design your systems such that each module is responsible (responds to) the needs of just that one business function.

Quindi riassumendo: una "responsabilità" è rivolta a una singola funzione aziendale. Se più di un attore potrebbe causare la necessità di cambiare classe, la classe probabilmente infrange questo principio.

    
risposta data 28.03.2017 - 17:04
fonte
2

Un buon articolo che spiega i principi di programmazione SOLID e fornisce esempi di codice che seguono e non seguono questi principi è link .

Nell'esempio relativo a SRP fornisce un esempio di alcune classi di forme (cerchio e quadrato) e una classe progettata per calcolare l'area totale di più forme.

Nel suo primo esempio crea la classe di calcolo dell'area e restituisce il suo output in formato HTML. Più tardi decide di mostrarlo come JSON e deve cambiare classe di calcolo dell'area.

Il problema con questo esempio è che la sua classe di calcolo dell'area è responsabile del calcolo dell'area delle forme e della visualizzazione di quell'area. Quindi passa attraverso un modo migliore per farlo utilizzando un'altra classe progettata specificamente per la visualizzazione delle aree.

Questo è un semplice esempio (e più facilmente comprensibile leggendo l'articolo in quanto contiene frammenti di codice) ma dimostra l'idea centrale di SRP.

    
risposta data 27.03.2017 - 22:21
fonte
0

Prima di tutto, quello che hai sono in realtà due problemi separati : il problema di quali metodi inserire nelle tue classi e il problema dell'interfaccia bloat.

Interfacce

Hai questa interfaccia:

public Interface CustomerCRUD
{
  public void Create(Customer customer);
  public Customer Read(int CustomerID);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Presumibilmente, esistono più classi conformi all'interfaccia CustomerCRUD (altrimenti un'interfaccia non è necessaria) e alcune funzioni do_crud(customer: CustomerCRUD) che contengono un oggetto conforme. Ma hai già infranto l'SRP: hai legato queste quattro operazioni distinte insieme.

Diciamo che in seguito dovresti operare sulle viste del database. Una vista del database ha solo il metodo Read disponibile per questo. Ma vuoi scrivere una funzione do_query_stuff(customer: ???) che operi in modo trasparente su tabelle o viste complete; dopotutto usa solo il metodo Read .

Quindi crea un'interfaccia

Public Interface CustomerReader   {     Lettura del cliente pubblico (customerID: int)   }

e considera l'interfaccia CustomerCrud come:

public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Ma non c'è fine in vista. Potrebbero esserci oggetti che possiamo creare ma non aggiornare, ecc. Questa tana di coniglio è troppo profonda. L'unico modo sensato per aderire al principio di responsabilità singola è quello di fare in modo che tutte le tue interfacce contengano esattamente un metodo . Go segue effettivamente questa metodologia da ciò che ho visto, con la stragrande maggioranza delle interfacce che contengono una singola funzione; se si desidera specificare un'interfaccia che contiene due funzioni, è necessario creare una nuova interfaccia che combini le due cose. Presto avrai un'esplosione combinatoria di interfacce.

L'uscita da questo pasticcio consiste nell'utilizzare la sottotipizzazione strutturale (implementata ad esempio in OCaml) invece delle interfacce (che sono una forma di sottotipizzazione nominale). Non definiamo le interfacce; invece, possiamo semplicemente scrivere una funzione

let do_customer_stuff customer = customer.read ... customer.update ...

che chiama qualsiasi metodo che ci piace. OCaml userà l'inferenza di tipo per determinare che possiamo passare in qualsiasi oggetto che implementa questi metodi. In questo esempio, verrebbe determinato che customer ha tipo <read: int -> unit, update: int -> unit, ...> .

Corsi

Questo risolve il pasticcio interfaccia ; ma dobbiamo ancora implementare le classi che contengono più metodi. Ad esempio, dovremmo creare due classi diverse, CustomerReader e CustomerWriter ? Che cosa succede se vogliamo cambiare il modo in cui le tabelle vengono lette (ad es. Ora memorizziamo le risposte in rosso in redis prima di recuperare i dati), ma ora come sono scritte? Se segui la questa catena di ragionamento fino alla sua conclusione logica, guidi inestricabilmente alla programmazione funzionale:)

    
risposta data 28.03.2017 - 00:14
fonte
0

Nella mia mente, la cosa più vicina a un SRP che mi viene in mente è un flusso di utilizzo. Se non si dispone di un flusso di utilizzo chiaro per una data classe, probabilmente la tua classe ha un odore di progettazione.

Un flusso di utilizzo sarebbe una determinata successione di chiamate al metodo che ti darebbe un risultato atteso (quindi verificabile). Fondamentalmente definisci una classe con i casi d'uso ottenuti con IMHO, ecco perché tutta la metodologia del programma si concentra sulle interfacce rispetto all'implementazione.

    
risposta data 28.03.2017 - 23:43
fonte
0

È per ottenere questo cambiamento di requisiti multipli, non è necessario che il componente modifichi .

Ma buona fortuna a capire a prima vista quando si sente parlare di SOLID.

Vedo molti commenti che dicono SRP e YAGNI possono contraddirsi a vicenda, ma YAGN sono applicati da TDD (GOOS, London School) mi ha insegnato a pensare e progettare i miei componenti dal punto di vista del cliente. Ho iniziato a progettare le mie interfacce con qual è il minimo che qualsiasi client vorrebbe che facesse, cioè quanto poco dovrebbe fare . E questo esercizio può essere fatto senza alcuna conoscenza di TDD.

Mi piace la tecnica descritta da Zio Bob (non ricordo da dove, purtroppo), che va qualcosa come:

Ask yourself, what does this class do?

Did your answer contain either of And or Or

If so, extract that part of the answer, it a responsibility of its own

Questa tecnica è assoluta e, come ha detto @svidgen, SRP è un giudizio, ma quando si apprende qualcosa di nuovo, gli assoluti sono i migliori, è più facile solo sempre fai qualcosa. Assicurati che il motivo per cui non ti separi sia; una stima istruita e non perché non sai come. Questa è l'arte e richiede esperienza.

Penso che molte delle risposte sembrino essere un argomento per disaccoppiare quando si parla di SRP .

SRP è non per assicurarti che una modifica non si propaghi lungo il grafico delle dipendenze.

In teoria, senza SRP , non ci sono dipendenze ...

Una modifica non dovrebbe causare molti cambiamenti nell'applicazione, ma abbiamo altri principi per questo. SRP tuttavia, migliora il Open Closed Principle . Questo principio riguarda più l'astrazione, tuttavia le astrazioni più piccole sono più facili da reimplementare .

Quindi, quando insegni a SOLID nel suo complesso, fai attenzione a insegnare che SRP ti permette di cambiare meno codice quando cambiano i requisiti, quando in realtà ti permette di scrivere meno nuovo codice.

    
risposta data 29.03.2017 - 16:29
fonte
0

Non c'è una risposta chiara a questo. Sebbene la domanda sia ristretta, le spiegazioni non lo sono.

Per me, è qualcosa come Rasoio di Occam se vuoi. È un ideale in cui cerco di misurare il mio codice attuale. È difficile inchiodarlo in parole semplici e semplici. Un'altra metafora sarebbe "un argomento" che è astratto, cioè difficile da cogliere, come "singola responsabilità". Una terza descrizione sarebbe »che riguarda un livello di astrazione«.

Che cosa significa praticamente?

Ultimamente uso uno stile di codifica che consiste principalmente di due fasi:

La fase I è descritta al meglio come caos creativo. In questa fase scrivo il codice mentre i pensieri fluiscono - cioè crudo e brutto.

La fase II è l'esatto contrario. È come pulire dopo un uragano. Questo richiede più lavoro e disciplina. E poi guardo il codice dal punto di vista di un designer.

Ora sto lavorando principalmente su Python, il che mi consente di pensare a oggetti e classi in seguito. Prima Fase I - Scrivo solo funzioni e le divulgo quasi in modo casuale in diversi moduli. In Fase II , dopo che ho iniziato a funzionare, ho esaminato più da vicino quale modulo si occupa di quale parte della soluzione. E mentre sfogliamo i moduli, mi vengono in mente argomenti . Alcune funzioni sono correlate tematicamente. Questi sono buoni candidati per classi . E dopo aver trasformato le funzioni in classi - che è quasi finito con indentazione e aggiungendo self alla lista dei parametri in python;) - Io uso SRP come Rasoio di Occam per togliere funzionalità ad altri moduli e classi.

Un esempio corrente potrebbe essere scrivere una piccola funzionalità di esportazione l'altro giorno.

C'era bisogno di csv , excel e combinato excel sheets in un file zip.

La semplice funzionalità è stata eseguita in tre visualizzazioni (= funzioni). Ogni funzione utilizzava un metodo comune per determinare i filtri e un secondo metodo per recuperare i dati. Quindi in ciascuna funzione è stata effettuata la preparazione dell'esportazione ed è stata fornita come risposta dal server.

Ci sono stati troppi livelli di astrazione mescolati:

I) che si occupa di richiesta / risposta in entrata / uscita

II) determinazione dei filtri

III) recupero dei dati

IV) trasformazione dei dati

Il semplice passo era usare un'astrazione ( exporter ) per gestire i livelli II-IV in un primo passaggio.

L'unico rimedio era l'argomento che trattava le richieste / risposte . Allo stesso livello di astrazione è estraendo i parametri di richiesta che va bene. Così ho avuto per questa vista una "responsabilità".

In secondo luogo, ho dovuto interrompere l'esportatore, che come abbiamo visto era costituito da almeno altri tre livelli di astrazione.

La determinazione dei criteri di filtro e il effettivo sono quasi allo stesso livello di astrazione (i filtri sono necessari per ottenere il sottoinsieme giusto dei dati). Questi livelli sono stati inseriti in qualcosa di simile a un livello di accesso ai dati .

Nel prossimo passo ho diviso i meccanismi di esportazione effettivi: dove era necessario scrivere su un file temporale, l'ho suddiviso in due "responsabilità": una per la scrittura effettiva dei dati su disco e un'altra parte che trattava del formato attuale.

Lungo la formazione delle classi e dei moduli, le cose divennero più chiare, ciò che apparteneva a dove. E sempre la domanda latente, se la classe fa troppo .

How do you determine which responsibilities each class should have, and how do you define a responsibility in the context of SRP?

È difficile dare una ricetta da seguire. Ovviamente potrei ripetere il criptico »livello di astrazione« - regola se questo aiuta.

Principalmente per me è una sorta di "intuizione artistica" che porta all'attuale design; Il codice del modello come un artista può scolpire l'argilla o dipingere.

Immaginami come Coding Bob Ross ;)

    
risposta data 30.03.2017 - 21:50
fonte
0

Quello che cerco di fare per scrivere il codice che segue l'SRP:

  • Scegli un problema specifico che devi risolvere;
  • Scrivi il codice che lo risolve, scrivi tutto in un unico metodo (es .: main);
  • Analizzare attentamente il codice e, in base al business, cercare di definire le responsabilità che sono visibili in tutte le operazioni che vengono eseguite (questa è la parte soggettiva che dipende anche dal business / progetto / cliente);
  • Si prega di notare che tutte le funzionalità sono già state implementate; la prossima è solo l'organizzazione del codice (nessuna funzione o meccanismo aggiuntivo sarà implementato da ora in avanti in questo approccio);
  • In base alle responsabilità definite nei passaggi precedenti (definiti in base al business e all'idea di "un motivo per cambiare"), estrai una classe o un metodo separati per ciascuno;
  • Si noti che questo approccio riguarda solo l'SPR; idealmente dovrebbero esserci ulteriori passaggi qui cercando di aderire anche agli altri principi.

Esempio:

Problema: ottieni due numeri dall'utente, calcoli la loro somma e invia il risultato all'utente:

//first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}

Successivamente, provare a definire le responsabilità in base alle attività che devono essere eseguite. Da questo, estrai le classi appropriate:

//Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}

Quindi, il programma refactored diventa:

//Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}

Nota: questo esempio molto semplice prende in considerazione solo il principio SRP. L'utilizzo degli altri principi (ad esempio: la "L" - il codice dovrebbe dipendere dalle astrazioni piuttosto che dalle concrezioni) fornirebbe più vantaggi al codice e lo renderà più mantenibile per i cambiamenti di business.

    
risposta data 03.10.2017 - 21:07
fonte
0

Dal libro di Robert C. Martins Architettura pulita: guida di un artigiano alla struttura del software and Design , pubblicato il 10 settembre 2017, Robert scrive a pagina 62 quanto segue:

Historically, the SRP has been described this way:

A module should have one, and only one, reason to change

Software systems are changed to satisfy users and stakeholders; those users and stakeholders are the "reason to change". that the principle is talking about. Indeed, we can rephrase the principle to say this:

A module should be responsible to one, and only one, user or stakeholder

Unfortunately, the word "user" and "stakeholder" aren't really the right word to use here. There will likely be more than one user or stakeholder who wants the system changed in the sane way. Instead we're really referring to a group - one or more people who require that change. We'll refer to that group as an actor.

Thus the final version of the SRP is:

A module should be responsible to one, and only one, actor.

Quindi non si tratta di codice. L'SRP riguarda il controllo del flusso dei requisiti e delle esigenze aziendali, che possono venire solo da un soure.

    
risposta data 23.07.2018 - 22:10
fonte