MVC: O Controlador quebra o Princípio de Responsabilidade Única?

16

O Princípio da Responsabilidade Única declara que "uma classe deve ter um motivo para a mudança".

No padrão MVC, o trabalho do Controlador é mediar entre a Visualização e o Modelo. Ele oferece uma interface para o View relatar ações feitas pelo usuário na GUI (por exemplo, permitindo que o View chame controller.specificButtonPressed()) e pode chamar os métodos apropriados no Modelo para manipular seus dados ou invocar suas operações (por exemplo model.doSomething()) .

Isso significa que:

  • O Controller precisa conhecer a GUI, a fim de oferecer ao View uma interface adequada para relatar ações do usuário.
  • Ele também precisa conhecer a lógica do Modelo, para poder invocar os métodos apropriados no Modelo.

Isso significa que há duas razões para mudar : uma alteração na GUI e uma alteração na lógica de negócios.

Se a GUI mudar, por exemplo, um novo botão for adicionado, o Controlador poderá precisar adicionar um novo método para permitir que a View relate um usuário pressionando esse botão.

E se a lógica de negócios no Modelo mudar, o Controlador poderá ter que mudar para invocar os métodos corretos no Modelo.

Portanto, o controlador tem dois motivos possíveis para mudar . Isso quebra o SRP?

Aviv Cohn
fonte
2
Um controlador é uma via de mão dupla, não é sua abordagem típica de cima para baixo ou de baixo para cima. Não há possibilidade de abstrair uma de suas dependências porque o controlador é a própria abstração. Devido à natureza do padrão, não é possível aderir ao SRP aqui. Em resumo: sim, viola o SRP, mas isso é inevitável.
Jeroen Vannevel
1
Qual é o objetivo da pergunta? Se todos respondermos "sim, sim", e então? E se a resposta for "não"? Qual é o verdadeiro problema que você está tentando resolver com esta pergunta?
Bryan Oakley
1
"uma razão para mudar" não significa "código que muda". Se você digitar sete erros de digitação nos nomes de variáveis ​​da classe, essa classe agora tem 7 responsabilidades? Não. Se você tiver mais de uma variável ou mais de uma função, também poderá ter apenas uma única responsabilidade.
Bob

Respostas:

14

Se você continuar, consequentemente, discutindo sobre o SRP, perceberá que "responsabilidade única" é na verdade um termo esponjoso. Nosso cérebro humano é de alguma maneira capaz de distinguir entre responsabilidades diferentes e múltiplas responsabilidades podem ser abstraídas em uma responsabilidade "geral". Por exemplo, imagine que em uma família comum de quatro pessoas exista um membro da família responsável por fazer o café da manhã. Agora, para fazer isso, é necessário ferver ovos e torradas de pão e, é claro, preparar uma xícara saudável de chá verde (sim, o melhor é o chá verde). Dessa forma, você pode dividir "fazendo café da manhã" em pedaços menores, que são abstraídos para "fazer café da manhã". Observe que cada peça também é uma responsabilidade que pode, por exemplo, ser delegada a outra pessoa.

Voltando ao MVC: se mediar entre modelo e exibição não é uma responsabilidade, mas duas, então qual seria a próxima camada de abstração acima, combinando essas duas? Se você não consegue encontrar um, não o abstrai corretamente ou não existe nenhum, o que significa que você acertou tudo. E eu sinto que é o caso de um controlador, lidando com uma visão e um modelo.

valenterry
fonte
1
Apenas como uma nota adicional. Se você tiver controller.makeBreakfast () e controller.wakeUpFamily (), esse controlador quebraria o SRP, mas não por ser um controlador, apenas porque tem mais de uma responsabilidade.
Bob
Obrigado por responder, não sei se estou te seguindo. Você concorda que o controlador tem mais de uma responsabilidade? A razão pela qual penso assim é porque há duas razões para mudar (eu acho): uma mudança no modelo e uma mudança na exibição. Você concorda com isso?
Aviv Cohn
1
Sim, posso concordar que o controlador tem mais de uma responsabilidade. No entanto, essas são responsabilidades "inferiores" e isso não é um problema, porque no nível de abstração mais alto ele tem apenas uma responsabilidade (combinando as mais baixas mencionadas) e, portanto, não viola o SRP.
5159 valenterry
1
Encontrar o nível certo de abstração é definitivamente importante. O exemplo "preparar café da manhã" é bom - para concluir uma única responsabilidade, muitas vezes há uma série de tarefas que devem ser realizadas. Enquanto o controlador estiver apenas orquestrando essas tarefas, ele seguirá o SRP. Mas se souber muito sobre ferver ovos, fazer torradas ou preparar chá, isso violaria o SRP.
Allan
Esta resposta faz sentido para mim. Obrigado Valenterry.
J86
9

Se uma classe tem "duas razões possíveis para mudar", sim, ela viola o SRP.

Um controlador geralmente deve ser leve e ter a responsabilidade única de manipular o domínio / modelo em resposta a algum evento controlado por GUI. Podemos considerar cada uma dessas manipulações como basicamente casos de uso ou recursos.

Se um novo botão for adicionado à GUI, o controlador deverá mudar apenas se esse novo botão representar algum novo recurso (ou seja, oposto ao mesmo botão que existia na tela 1, mas ainda não existia na tela 2, e é então adicionado à tela 2). Também seria necessário haver uma nova alteração correspondente no modelo, para suportar essa nova funcionalidade / recurso. O controlador ainda tem a responsabilidade de manipular o domínio / modelo em resposta a algum evento controlado por GUI.

Se a lógica de negócios do modelo for alterada devido à correção de um bug e exigir que o controlador seja alterado, esse é um caso especial (ou talvez o modelo esteja violando o principal aberto-fechado). Se a lógica de negócios no modelo mudar para oferecer suporte a alguma nova funcionalidade / recurso, isso não afetará necessariamente o controlador - apenas se o controlador precisar expor esse recurso (o que quase sempre seria o caso, caso contrário, por que ele seria adicionado a o modelo de domínio, se não for usado). Portanto, neste caso, o controlador também deve ser modificado, para suportar a manipulação do modelo de domínio dessa nova maneira, em resposta a algum evento controlado por GUI.

Se o controlador tiver que mudar porque, digamos, a camada de persistência é alterada de um arquivo simples para um banco de dados, o controlador certamente está violando o SRP. Se o controlador sempre trabalha na mesma camada de abstração, isso pode ajudar a alcançar o SRP.

Jordânia
fonte
4

O controlador não viola o SRP. Como você afirma, sua responsabilidade é mediar entre os modelos e a visualização.

Dito isto, o problema com o seu exemplo é que você está vinculando os métodos do controlador à lógica na visualização, ou seja controller.specificButtonPressed. Nomeando os métodos dessa maneira, vincula o controlador à sua GUI, você não conseguiu abstrair as coisas corretamente. O controlador deve executar ações específicas, controller.saveDataou seja, ou controller.retrieveEntry. Adicionar um novo botão na GUI não significa necessariamente adicionar um novo método ao controlador.

Pressionar um botão na visualização significa fazer alguma coisa, mas o que quer que seja poderia facilmente ter sido acionado de várias maneiras ou mesmo não através da visualização.

No artigo da Wikipedia sobre SRP

Martin define uma responsabilidade como uma razão para mudar e conclui que uma classe ou módulo deve ter uma e apenas uma razão para mudar. Como exemplo, considere um módulo que compila e imprime um relatório. Esse módulo pode ser alterado por dois motivos. Primeiro, o conteúdo do relatório pode mudar. Segundo, o formato do relatório pode mudar. Essas duas coisas mudam por causas muito diferentes; um substantivo e um cosmético. O princípio da responsabilidade única diz que esses dois aspectos do problema são realmente duas responsabilidades separadas e, portanto, devem estar em classes ou módulos separados. Seria um péssimo design juntar duas coisas que mudam por razões diferentes em momentos diferentes.

O controlador não está preocupado com o que está na exibição apenas quando um de seus métodos é chamado, ele fornece dados especificados para a exibição. Ele só precisa saber sobre a funcionalidade do modelo, desde que saiba que precisa chamar os métodos que eles terão. Não sabe nada além disso.

Saber que um objeto tem um método disponível para chamar não é o mesmo que conhecer sua funcionalidade.

Schleis
fonte
1
A razão pela qual pensei que o controlador deveria incluir métodos como esse specificButtonsPressed()é porque li que a visualização não deveria saber nada sobre a funcionalidade dos botões e outros elementos da GUI. Fui ensinado que, quando um botão é pressionado, a exibição deve simplesmente se reportar ao controlador, e o controlador deve decidir 'o que significa' (e depois invocar os métodos apropriados no modelo). Fazer a chamada de visualização controller.saveData()significa que a visualização precisa saber o que significa pressionar esse botão, além do fato de ter sido pressionado.
Aviv Cohn
1
Se eu abandonar a ideia de separação completa entre pressionar um botão e seu significado (o que resulta no controlador com métodos como esse specificButtonPressed()), na verdade o controlador não estaria tão ligado à GUI. Devo abandonar os specificButtonPressed()métodos? A vantagem que acho que ter esses métodos faz sentido para você? Ou ter buttonPressed()métodos no controlador não vale a pena?
Aviv Cohn
1
Em outras palavras (desculpe pelos longos comentários): Eu acho que a vantagem de ter specificButtonPressed()métodos no controlador é que ele separa a View do significado de pressionar o botão completamente . No entanto, a desvantagem é que ele vincula o controlador à GUI em certo sentido. Qual abordagem é melhor?
Aviv Cohn
@prog IMO O controlador deve estar cego para a visualização. Um botão terá algum tipo de funcionalidade, mas não precisa saber seus detalhes. Só precisa saber que está enviando alguns dados para um método de controlador. A esse respeito, o nome não importa. Pode ser chamado fooou com a mesma facilidade fireZeMissiles. Ele saberá apenas que deve reportar a uma função específica. Ele não sabe o que a função faz, apenas a chamará. O controlador não está preocupado com a forma como seus métodos são chamados, apenas que ele responderá de uma certa maneira quando for.
Schleis 04/04
1

Uma responsabilidade única dos controladores é o contrato que medeia entre a visualização e o modelo. A Visualização deve ser responsável apenas pela exibição, o modelo deve ser responsável apenas pela lógica de negócios. É responsabilidade dos controladores colmatar essas duas responsabilidades.

Tudo bem, mas se afastar um pouco da academia; um controlador no MVC geralmente é composto por muitos métodos de ação menores. Essas ações geralmente correspondem a coisas que uma coisa pode fazer. Se estou vendendo produtos, provavelmente terei um ProductController. Esse controlador terá ações como GetReviews, ShowSpecs, AddToCart ect ...

O modo de exibição tem o SRP de exibição da interface do usuário e parte dessa interface inclui um botão que diz AddToCart.

O controlador tem o SRP de conhecer todas as visualizações e modelos envolvidos no processo.

A ação AddToCart dos controladores tem o SRP específico de conhecer todos que precisam estar envolvidos quando um item é adicionado a um carrinho.

O Modelo de Produto possui o SRP da modelagem da lógica do produto e o ShoppingCart Model possui o SRP da modelagem de como os itens são salvos para check-out posterior. O Modelo do Usuário possui um SRP para modelar o usuário que está adicionando itens ao carrinho.

Você pode e deve reutilizar modelos para realizar seus negócios e esses modelos precisam ser acoplados em algum momento do seu código. O controlador controla cada maneira única em que o acoplamento acontece.

WhiteleyJ
fonte
0

Na verdade, os controladores têm apenas uma responsabilidade: alterar o estado dos aplicativos com base na entrada do usuário.

Um controlador pode enviar comandos para o modelo para atualizar o estado do modelo (por exemplo, editar um documento). Também pode enviar comandos para a visualização associada para alterar a apresentação da visualização do modelo (por exemplo, rolando um documento).

source: wikipedia

Se você está tendo "controladores" no estilo Rails (que manipulam instâncias de registro ativas e modelos burros) , é claro que estão quebrando o SRP.

Por outro lado, os aplicativos no estilo Rails não são realmente MVC para começar.

mefisto
fonte