MVC: il controller infrange il principio di responsabilità singola?


16

Il principio di responsabilità singola afferma che "una classe dovrebbe avere una ragione per cambiare".

Nel modello MVC, il compito del controller è quello di mediare tra la vista e il modello. Offre un'interfaccia per la vista per segnalare le azioni eseguite dall'utente sulla GUI (ad esempio consentendo alla vista di chiamare controller.specificButtonPressed()) ed è in grado di chiamare i metodi appropriati sul modello per manipolare i suoi dati o invocare le sue operazioni (ad es. model.doSomething()) .

Ciò significa che:

  • Il controller deve conoscere la GUI, al fine di offrire alla View un'interfaccia adatta per segnalare le azioni dell'utente.
  • Deve inoltre conoscere la logica del Modello, al fine di poter invocare i metodi appropriati sul Modello.

Ciò significa che ha due motivi per cambiare : un cambiamento nella GUI e un cambiamento nella logica aziendale.

Se la GUI cambia, ad es. Viene aggiunto un nuovo pulsante, il Controller potrebbe dover aggiungere un nuovo metodo per consentire alla Vista di segnalare a un utente di premere questo pulsante.

E se la logica aziendale nel Modello cambia, il Controller potrebbe dover cambiare per invocare i metodi corretti sul Modello.

Pertanto, il controller ha due possibili ragioni per cambiare . Rompe SRP?


2
Un controller è una strada a doppio senso, non è il tipico approccio top-down o bottom-up. Non è possibile per lui astrarre una delle sue dipendenze perché il controller è l'astrazione stessa. A causa della natura del modello, qui non è possibile aderire all'SRP. In breve: sì, viola SRP ma è inevitabile.
Jeroen Vannevel,

1
Qual è il punto della domanda? Se tutti rispondiamo "sì, lo fa", e allora? Cosa succede se la risposta è "no"? Qual è il vero problema che stai cercando di risolvere con questa domanda?
Bryan Oakley,

1
"un motivo per cambiare" non significa "codice che cambia". Se fai sette errori di battitura nei nomi delle variabili per la classe, quella classe ora ha 7 responsabilità? No. Se hai più di una variabile o più di una funzione, potresti anche avere una sola responsabilità.
Bob,

Risposte:


14

Se continui di conseguenza a ragionare sull'SRP, noterai che "responsabilità singola" è in realtà un termine spugnoso. Il nostro cervello umano è in qualche modo in grado di distinguere tra responsabilità diverse e molteplici responsabilità possono essere astratte in una responsabilità "generale". Ad esempio, immagina che in una famiglia di 4 persone comune ci sia un membro della famiglia responsabile della colazione. Ora, per fare questo, bisogna far bollire le uova e tostare il pane e ovviamente preparare una sana tazza di tè verde (sì, il tè verde è il migliore). In questo modo puoi spezzare il "fare colazione" in pezzi più piccoli che sono astratti insieme per "preparare la colazione". Si noti che ogni pezzo è anche una responsabilità che potrebbe ad esempio essere delegata a un'altra persona.

Ritorno al MVC: se la mediazione tra modello e vista non è una responsabilità ma due, allora quale sarebbe il prossimo livello di astrazione sopra, combinando quei due? Se non riesci a trovarne uno, non l'hai estratto in modo corretto o non ce n'è nessuno, il che significa che hai capito bene. E penso che sia il caso di un controller, che gestisce una vista e un modello.


1
Proprio come una nota aggiunta. Se hai controller.makeBreakfast () e controller.wakeUpFamily (), allora quel controller avrebbe rotto SRP, ma non perché è un controller, solo perché ha più di una responsabilità.
Bob,

Grazie per aver risposto, non sono sicuro di seguirti. Accetti che il responsabile del trattamento abbia più di una responsabilità? La ragione per cui la penso così è perché ha due ragioni per cambiare (penso): un cambiamento nel modello e un cambiamento nella vista. Sei d'accordo con questo?
Aviv Cohn,

1
Sì, posso essere d'accordo sul fatto che il responsabile del trattamento abbia più di una responsabilità. Tuttavia, si tratta di responsabilità "inferiori" e questo non è un problema perché al massimo livello di astrazione ha una sola responsabilità (combinando quelle inferiori che menzioni) e quindi non viola l'SRP.
valenterry,

1
Trovare il giusto livello di astrazione è sicuramente importante. L'esempio "prepara la colazione" è positivo: per completare una singola responsabilità ci sono spesso una serie di compiti che devono essere svolti. Finché il controller sta solo orchestrando queste attività, segue SRP. Ma se sa troppo sull'ebollizione delle uova, sulla preparazione del pane tostato o sulla preparazione del tè, ciò violerebbe l'SRP.
Allan,

Questa risposta ha senso per me. Grazie Valenterry.
J86,

9

Se una classe ha "due possibili ragioni per cambiare", allora sì, viola SRP.

Un controller di solito dovrebbe essere leggero e avere la sola responsabilità di manipolare il dominio / modello in risposta ad alcuni eventi guidati dalla GUI. Possiamo considerare ciascuna di queste manipolazioni essenzialmente come casi d'uso o funzionalità.

Se viene aggiunto un nuovo pulsante sulla GUI, il controller dovrebbe cambiare solo se quel nuovo pulsante rappresenta una nuova funzionalità (cioè al contrario dello stesso pulsante che esisteva sullo schermo 1 ma non esisteva ancora sullo schermo 2, ed è quindi aggiunto alla schermata 2). Dovrebbe esserci anche una nuova modifica corrispondente nel modello, per supportare questa nuova funzionalità / caratteristica. Il controller ha ancora la responsabilità di manipolare il dominio / modello in risposta ad un evento guidato dalla GUI.

Se la logica aziendale nel modello cambia a causa della correzione di un bug e richiede la modifica del controller, questo è un caso speciale (o forse il modello sta violando il principio aperto-chiuso). Se la logica aziendale nel modello cambia per supportare alcune nuove funzionalità / funzionalità, ciò non influisce necessariamente sul controller - solo se il controller deve esporre quella funzionalità (che sarebbe quasi sempre il caso, altrimenti perché dovrebbe essere aggiunto a il modello di dominio se non verrà utilizzato). Quindi, in questo caso, anche il controller deve essere modificato, per supportare la manipolazione del modello di dominio in questo nuovo modo in risposta ad un evento guidato dalla GUI.

Se il controller deve cambiare perché, ad esempio, il livello di persistenza viene modificato da un file flat a un database, allora il controller sta sicuramente violando SRP. Se il controller funziona sempre allo stesso livello di astrazione, ciò può aiutare a raggiungere SRP.


4

Il controller non viola SRP. Come si afferma, la sua responsabilità è quella di mediare tra i modelli e la vista.

Detto questo, il problema con il tuo esempio è che stai legando i metodi del controller alla logica nella vista, ad es controller.specificButtonPressed. Denominando i metodi in questo modo si collega il controller alla GUI, non si è riusciti a astrarre correttamente le cose. Il responsabile del trattamento dovrebbe eseguire azioni specifiche, ovvero controller.saveDatao controller.retrieveEntry. Aggiungere un nuovo pulsante nella GUI non significa necessariamente aggiungere un nuovo metodo al controller.

Premendo un pulsante nella vista, significa fare qualcosa, ma qualunque cosa sia potrebbe essere stata facilmente innescata in un numero qualsiasi di altre direzioni o addirittura non attraverso la vista.

Dall'articolo di Wikipedia su SRP

Martin definisce una responsabilità come una ragione per cambiare, e conclude che una classe o un modulo dovrebbe avere una, e una sola, ragione per cambiare. Ad esempio, si consideri un modulo che compila e stampa un rapporto. Tale modulo può essere modificato per due motivi. Innanzitutto, il contenuto del rapporto può cambiare. In secondo luogo, il formato del rapporto può cambiare. Queste due cose cambiano per cause molto diverse; uno sostanziale e uno cosmetico. Il principio della singola responsabilità afferma che questi due aspetti del problema sono in realtà due responsabilità separate e dovrebbero quindi essere in classi o moduli separati. Sarebbe una cattiva progettazione accoppiare due cose che cambiano per motivi diversi in momenti diversi.

Il controller non si preoccupa di ciò che è nella vista solo che quando viene chiamato uno dei suoi metodi che fornisce dati specifici alla vista. Deve solo conoscere le funzionalità del modello in quanto sa che deve chiamare i metodi che avranno. Non sa altro.

Sapere che un oggetto ha un metodo disponibile da chiamare non è lo stesso che conoscerne la funzionalità.


1
Il motivo per cui ho pensato che il controller dovesse includere metodi simili specificButtonsPressed()è perché ho letto che la vista non dovrebbe sapere nulla della funzionalità dei suoi pulsanti e di altri elementi della GUI. Mi è stato insegnato che quando si preme un pulsante, la vista dovrebbe semplicemente riferire al controller e il controller dovrebbe decidere "cosa significa" (e quindi invocare i metodi appropriati sul modello). Effettuare la chiamata vista controller.saveData()significa che la vista deve sapere cosa significa premere questo pulsante, oltre al fatto che è stato premuto.
Aviv Cohn,

1
Se abbandonassi l'idea di una separazione completa tra la pressione di un pulsante e il suo significato (il che si traduce in metodi simili specificButtonPressed()al controller), in effetti il ​​controller non sarebbe così legato alla GUI. Devo abbandonare i specificButtonPressed()metodi? Il vantaggio che ritengo avere questi metodi ha senso per te? O avere buttonPressed()metodi nel controller non vale la pena?
Aviv Cohn,

1
In altre parole (scusate i lunghi commenti): penso che il vantaggio di avere dei specificButtonPressed()metodi nel controller sia che separa la vista dal significato del suo pulsante che preme completamente . Tuttavia, lo svantaggio è che lega il controller alla GUI in un certo senso. Quale approccio è migliore?
Aviv Cohn,

@prog IMO Il controller dovrebbe essere cieco alla vista. Un pulsante avrà una sorta di funzionalità ma non ha bisogno di conoscerne i dettagli. Deve solo sapere che sta inviando alcuni dati a un metodo del controller. A questo proposito il nome non ha importanza. Può essere chiamato foo, o altrettanto facilmente fireZeMissiles. Saprà solo che dovrebbe riferire a una funzione specifica. Non sa come la funzione fa solo che la chiamerà. Il controller non si preoccupa di come vengono invocati i suoi metodi, ma solo che risponderà in determinate maniere.
Schleis,

1

Una singola responsabilità dei controllori è quella di essere il contratto che media tra la vista e il modello. La vista dovrebbe essere responsabile solo della visualizzazione, il modello dovrebbe essere responsabile solo della logica aziendale. È responsabilità dei controllori colmare queste due responsabilità.

Va bene e va bene, ma avventurarsi un po 'lontano dal mondo accademico; un controller in MVC è generalmente composto da molti metodi di azione più piccoli. Queste azioni generalmente corrispondono a cose che una cosa può fare. Se vendo prodotti, probabilmente avrò un ProductController. Quel controller avrà azioni come GetReviews, ShowSpecs, AddToCart ect ...

La vista ha l'SRP di visualizzazione dell'interfaccia utente e parte dell'interfaccia utente include un pulsante che dice AddToCart.

Il controller ha l'SRP di conoscere tutte le viste e i modelli coinvolti nel processo.

L'azione AddToCart dei controller ha lo specifico SRP di conoscere tutti coloro che devono essere coinvolti quando un articolo viene aggiunto a un carrello.

Il modello del prodotto ha l'SRP della modellazione della logica del prodotto e il modello ShoppingCart ha l'SRP della modellazione del modo in cui gli articoli vengono salvati per il checkout successivo. Il modello utente ha un SRP per modellare l'utente che sta aggiungendo elementi al proprio carrello.

Puoi e dovresti riutilizzare i modelli per svolgere la tua attività e quei modelli devono essere accoppiati ad un certo punto del tuo codice. Il controller controlla ogni modo unico in cui avviene l'accoppiamento.


0

I controller hanno effettivamente una sola responsabilità: modificare lo stato delle applicazioni in base all'input dell'utente.

Un controller può inviare comandi al modello per aggiornare lo stato del modello (ad esempio, modificando un documento). Può anche inviare comandi alla vista associata per modificare la presentazione della vista del modello (ad esempio, scorrendo un documento).

source: wikipedia

Se invece si hanno "controller" in stile Rails (che manipolano istanze di record attivi e modelli stupidi) , ovviamente stanno rompendo SRP.

Inoltre, le applicazioni in stile Rails non sono proprio MVC per cominciare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.