O que entra no "Controller" no "MVC"?

186

Acho que entendo os conceitos básicos do MVC - o Modelo contém os dados e o comportamento do aplicativo, o View é responsável por exibi-lo ao usuário e o Controller lida com a entrada do usuário. O que não tenho certeza é exatamente o que se passa no controlador.

Digamos, por exemplo, que eu tenho um aplicativo bastante simples (estou pensando especificamente em Java, mas suponho que os mesmos princípios se apliquem em outros lugares). Organizo meu código em 3 pacotes chamados app.model, app.viewe app.controller.

Dentro do app.modelpacote, tenho algumas classes que refletem o comportamento real do aplicativo. Eles extends Observableusam setChanged()e notifyObservers()acionam as visualizações para atualizar quando apropriado.

O app.viewpacote possui uma classe (ou várias classes para diferentes tipos de exibição) que usa javax.swingcomponentes para manipular a exibição. Alguns desses componentes precisam ser realimentados no modelo. Se bem entendi, o View não deve ter nada a ver com o feedback - isso deve ser tratado pelo Controlador.

Então, o que eu realmente coloco no Controller? Coloco o public void actionPerformed(ActionEvent e)na View com apenas uma chamada para um método no Controller? Em caso afirmativo, alguma validação etc deve ser feita no Controlador? Em caso afirmativo, como retorno as mensagens de erro de volta ao View - isso deve passar pelo Modelo novamente ou o Controlador deve enviá-lo diretamente de volta ao View?

Se a validação for feita na View, o que devo colocar no Controller?

Desculpe pela longa pergunta, eu só queria documentar minha compreensão do processo e espero que alguém possa esclarecer esse problema para mim!

Paul Walker
fonte

Respostas:

520

No exemplo que você sugeriu, você está certo: "o usuário clicou no botão 'excluir este item'" na interface basicamente deveria chamar a função "excluir" do controlador. O controlador, no entanto, não tem idéia de como é a exibição e, portanto, sua exibição deve coletar algumas informações como "qual item foi clicado?"

Em um formulário de conversa:

Ver : "Ei, controlador, o usuário acabou de me dizer que quer o item 4 excluído."
Controlador : "Hmm, depois de verificar suas credenciais, ele pode fazer isso ... Ei, modelo, quero que você pegue o item 4 e faça o que fizer para excluí-lo."
Modelo : "Item 4 ... entendi. Foi excluído. De volta para você, Controlador."
Controlador : "Aqui, vou coletar o novo conjunto de dados. De volta para você, vista."
Ver : "Legal, vou mostrar o novo conjunto para o usuário agora."

No final dessa seção, você tem uma opção: ou a visualização pode fazer uma solicitação separada, "fornece-me o conjunto de dados mais recente" e, portanto, é mais pura, ou o controlador retorna implicitamente o novo conjunto de dados com a opção "delete " Operação.

Andres Jaan Tack
fonte
90
Esse diálogo é a melhor explicação do MVC que encontrei, obrigado!
Paul Walker
13
Tudo bem, mas não há nada errado com a visualização da leitura diretamente do modelo. "Controladores não são policiais de dados". Há também uma doutrina que diz para manter os controladores magros. Os Assistentes da View são o local perfeito para coletar dados prontos para serem consumidos pela sua exibição. Não é necessário despachar a pilha completa do controlador para reutilizar alguma lógica de acesso a dados. Mais detalhes: rmauger.co.uk/2009/03/…
Exception e
1
Eu concordo com "Exceção e". Os dados no modelo podem ser atualizados por muitos eventos, não necessariamente pelo controlador e, portanto, em alguns projetos do MVC, o M sinaliza ao V que os dados estão sujos e o V pode se atualizar. OC não tem nenhum papel a desempenhar nesse caso.
Mishax
68

O problema MVCé que as pessoas pensam que a visão, o controlador e o modelo devem ser o mais independentes possível um do outro. Eles não pensam nisso como uma visão e um controlador frequentemente interligados M(VC).

O controlador é o mecanismo de entrada da interface do usuário, que geralmente é embaraçado na visualização, principalmente nas GUIs. No entanto, a visualização é emitida e o controlador é inserido. Uma visão geralmente pode funcionar sem um controlador correspondente, mas um controlador geralmente é muito menos útil sem uma visão. Os controladores amigáveis ​​usam a visualização para interpretar as informações do usuário de uma maneira mais significativa e intuitiva. É isso que dificulta a separação do conceito de controlador da visualização.

Pense em um robô controlado por rádio em um campo de detecção em uma caixa selada como o modelo.

O modelo é sobre transições de estado e estado sem conceito de saída (exibição) ou o que está acionando as transições de estado. Posso obter a posição do robô em campo e o robô sabe como mudar de posição (dê um passo à frente / para trás / esquerda / direita. Fácil de visualizar sem uma vista ou um controlador, mas não faz nada útil

Pense em uma visão sem um controlador, por exemplo, alguém em outra sala da rede em outra sala assistindo o robô se posicionar enquanto as coordenadas (x, y) fluem em um console de rolagem. Essa visualização está apenas exibindo o estado do modelo, mas esse cara não tem controlador. Novamente, é fácil visualizar essa visão sem um controlador.

Pense em um controlador sem visão, por exemplo, alguém trancado em um armário com o controlador de rádio sintonizado na frequência do robô. Este controlador está enviando entrada e causando transições de estado sem ter idéia do que está fazendo no modelo (se houver). Fácil de visualizar, mas não é realmente útil sem algum tipo de feedback da visualização.

A maioria das interfaces de usuário amigáveis ​​coordena a visualização com o controlador para fornecer uma interface de usuário mais intuitiva. Por exemplo, imagine uma visualização / controlador com uma tela sensível ao toque mostrando a posição atual do robô em 2-D e permitindo que o usuário toque no ponto na tela que está na frente do robô. O controlador precisa de detalhes sobre a visualização, por exemplo, a posição e a escala da viewport e a posição do pixel tocado em relação à posição do pixel do robô na tela) para interpretar isso corretamente (ao contrário do cara trancado no armário com o controlador de rádio).

Eu já respondi sua pergunta? :-)

O controlador é qualquer coisa que recebe entrada do usuário usada para fazer com que o modelo faça a transição do estado. Tente manter a visão e o controlador separados, mas perceba que eles geralmente são interdependentes entre si, por isso não há problema se o limite entre eles é nebuloso, ou seja, ter a visão e o controlador como pacotes separados pode não ser tão limpo quanto você faria. como, mas tudo bem. Você pode ter que aceitar que o controlador não será separado da visão como é do modelo.

... qualquer validação etc deve ser feita no controlador? Em caso afirmativo, como retorno as mensagens de erro de volta ao View - isso deve passar pelo Modelo novamente ou o Controlador deve enviá-lo diretamente de volta ao View?

Se a validação for feita na View, o que eu coloco no Controller?

Eu digo que uma visão vinculada e um controlador devem interagir livremente sem passar pelo modelo. O controlador recebe a entrada do usuário e deve fazer a validação (talvez usando informações do modelo e / ou da visualização), mas se a validação falhar, o controlador poderá atualizar sua visualização relacionada diretamente (por exemplo, mensagem de erro).

O teste de ácido para isso é perguntar a si mesmo se uma visão independente (ou seja, o cara na outra sala assistindo a posição do robô pela rede) deve ver alguma coisa ou não como resultado do erro de validação de outra pessoa (por exemplo, o cara no armário) tentou dizer ao robô para sair do campo). Geralmente, a resposta é não - o erro de validação impediu a transição de estado. Se não houve transição de estado (o robô não se moveu), não há necessidade de informar os outros pontos de vista. O cara no armário simplesmente não recebeu nenhum feedback de que ele tentou causar uma transição ilegal (sem visão - interface do usuário ruim), e ninguém mais precisa saber disso.

Se o cara com a tela sensível ao toque tentou enviar o robô para fora do campo, ele recebeu uma boa mensagem amigável pedindo que não matasse o robô enviando-o para fora do campo de detecção, mas, novamente, ninguém mais precisa saber disso.

Se outros pontos de vista que precisa de saber sobre esses erros, então você está efetivamente dizendo que as entradas do usuário e quaisquer erros resultantes são parte do modelo e toda a coisa é um pouco mais complicado ...

Bert F
fonte
23

Aqui está um bom artigo sobre o básico do MVC.

Afirma ...

Controlador - O controlador converte interações com a visualização em ações a serem executadas pelo modelo.

Em outras palavras, sua lógica de negócios. O controlador responde às ações do usuário executadas na visualização e responde. Coloque a validação aqui e selecione a exibição apropriada se a validação falhar ou for bem-sucedida (página de erro, caixa de mensagem, o que for).

Há outro bom artigo na Fowler .

JP Alioto
fonte
O MVP é outra opção discutida no artigo que você faz referência, consulte martinfowler.com/eaaDev/ModelViewPresenter.html
Jon
Obrigado pelos links, eles certamente contribuem para uma leitura interessante.
Paul Walker
18

O padrão MVC deseja apenas que você separe a apresentação (= visualização) da lógica de negócios (= modelo). A parte do controlador está lá apenas para causar confusão.

Dimitri C.
fonte
1
Exatamente, o que eu sempre pensei até agora, mas nunca tive coragem de contar a ninguém ... ou pode não ser, não conseguiu inventar palavras adequadas.
User1451111
1
Model-View-Confusion
Chovendo
10

Na prática, nunca achei o conceito de controlador particularmente útil. Eu uso uma separação estrita de modelo / exibição no meu código, mas não há um controlador claramente definido. Parece ser uma abstração desnecessária.

Pessoalmente, o MVC completo parece o padrão de design de fábrica, pois leva facilmente a um design confuso e muito complicado. Não seja um astronauta de arquitetura .

John Kugelman
fonte
9

Com base na sua pergunta, tenho a impressão de que você está um pouco confuso com o papel do modelo. O Modelo é fixado nos dados associados ao aplicativo; se o aplicativo tiver um banco de dados, o trabalho do modelo será conversar com ele. Ele também manipulará qualquer lógica simples associada a esses dados; se você tem uma regra que diz que em todos os casos em que TABLE.foo == "Viva!" e TABLE.bar == "Huzzah!" depois defina TABLE.field = "W00t!", então você quer que o Model cuide dele.

O Controller é o que deve lidar com a maior parte do comportamento do aplicativo. Então, para responder às suas perguntas:

Coloco o actionPerformed nulo público (ActionEvent e) na View com apenas uma chamada para um método no Controller?

Eu diria que não. Eu diria que deveria morar no Controller; o View deve simplesmente alimentar os dados provenientes da interface do usuário no Controller e deixar o Controller decidir quais métodos devem ser chamados em resposta.

Em caso afirmativo, alguma validação etc deve ser feita no Controlador?

A maior parte da sua validação realmente deve ser feita pelo Controlador; deve responder à questão de saber se os dados são válidos ou não e, se necessário, alimentar as mensagens de erro apropriadas para a Visualização. Na prática, você pode incorporar algumas verificações simples de sanidade na camada Exibir para melhorar a experiência do usuário. (Estou pensando principalmente em ambientes da web, nos quais você pode querer exibir uma mensagem de erro no momento em que o usuário clica em "Enviar" em vez de aguardar todo o ciclo de envio -> processo -> carregamento da página antes de dizer a eles que estão errados .) Apenas tenha cuidado; você não deseja duplicar esforços além do necessário, e em muitos ambientes (novamente, estou pensando na web), muitas vezes é necessário tratar todos os dados provenientes da interface do usuário como um pacote de imundos e imundos. mente até você

Em caso afirmativo, como retorno as mensagens de erro de volta ao View - isso deve passar pelo Modelo novamente ou o Controlador deve enviá-lo diretamente de volta ao View?

Você deve ter algum protocolo configurado em que o View não saiba necessariamente o que acontece a seguir até que o Controller informe. Qual tela você mostra depois que o usuário aperta esse botão? O View pode não saber, e o Controller também pode não saber até olhar para os dados que acabou de obter. Pode ser "Vá para outra tela, conforme o esperado" ou "Permaneça nesta tela e exiba esta mensagem de erro".

Na minha experiência, a comunicação direta entre o Modelo e a Visualização deve ser muito, muito limitada, e a Visualização não deve alterar diretamente nenhum dado do Modelo; esse deve ser o trabalho do controlador.

Se a validação for feita na View, o que devo colocar no Controller?

Veja acima; a validação real deve estar no controlador. E espero que você tenha alguma idéia do que deve ser colocado no Controlador agora. :-)

Vale a pena notar que tudo pode ficar um pouco embaçado nas bordas; como em quase tudo tão complexo quanto a engenharia de software, haverá muitas solicitações de julgamento. Apenas use seu bom senso, tente manter a consistência dentro deste aplicativo e tente aplicar as lições que aprenderá no próximo projeto.

BlairHippo
fonte
7

O controlador é realmente parte do modo de exibição. Seu trabalho é descobrir quais serviços são necessários para atender à solicitação, desmarcar valores da View em objetos que a interface de serviço requer, determinar a próxima View e organizar a resposta de volta para um formato que a próxima View possa usar . Ele também lida com todas as exceções lançadas e as renderiza em Visualizações que os usuários podem entender.

A camada de serviço é o que conhece os casos de uso, unidades de trabalho e objetos de modelo. O controlador será diferente para cada tipo de visualização - você não terá o mesmo controlador para UIs de desktop, navegador, Flex ou móvel. Então eu digo que é realmente parte da interface do usuário.

Orientado a serviços: é aí que o trabalho é feito.

duffymo
fonte
3

O controlador é principalmente para coordenação entre a vista e o modelo.

Infelizmente, às vezes acaba sendo misturado com a exibição - em aplicativos pequenos, embora isso não seja tão ruim.

Eu sugiro que você coloque o:

public void actionPerformed(ActionEvent e)

no controlador. Em seguida, seu ouvinte de ação na sua exibição deve delegar para o controlador.

Quanto à parte da validação, você pode colocá-lo na visualização ou no controlador, pessoalmente acho que ele pertence ao controlador.

Eu recomendaria definitivamente dar uma olhada no Passive View e no Supervising Presenter (que é essencialmente o que o Model View Presenter está dividido - pelo menos por Fowler). Vejo:

http://www.martinfowler.com/eaaDev/PassiveScreen.html

http://www.martinfowler.com/eaaDev/SupervisingPresenter.html

Jon
fonte
3

Aqui está uma regra prática que eu uso: se for um procedimento que usarei especificamente para uma ação nesta página, ele pertence ao controlador, não ao modelo. O modelo deve fornecer apenas uma abstração coerente para o armazenamento de dados.

Eu inventei isso depois de trabalhar com um aplicativo da Web grande escrito por desenvolvedores que pensavam que eram entendidos em MVC, mas realmente não. Seus "controladores" são reduzidos a oito linhas de chamada de métodos de classe estática que normalmente não são chamados em nenhum outro lugar: - / tornando seus modelos pouco mais do que maneiras de criar espaços para nome. A refatoração correta faz três coisas: muda todo o SQL para a camada de acesso a dados (também conhecido como modelo), torna o código do controlador um pouco mais detalhado, mas muito mais compreensível, e reduz os antigos arquivos "modelo" a nada. :-)

staticsan
fonte
1

Observe também que cada widget Swing pode ser considerado como contendo os três componentes MVC: cada um possui um Model (por exemplo, ButtonModel), uma View (BasicButtonUI) e um Control (o próprio JButton).

akf
fonte
1

Você está essencialmente certo sobre o que coloca no controlador. É a única maneira que o Modelo deve interagir com a Visualização. O actionperformed pode ser colocado na View, mas a funcionalidade real pode ser colocada em outra classe que atuaria como Controller. Se você fizer isso, recomendo examinar o padrão Command, que é uma maneira de abstrair todos os comandos que possuem o mesmo receptor. Desculpe pela digressão.

De qualquer forma, uma implementação MVC adequada terá apenas as seguintes interações: Modelo -> Exibir Visualização -> Controller Controller -> View

O único lugar em que pode haver outra interação é se você usar um observador para atualizar a Visualização, então a Visualização precisará solicitar ao Controlador as informações necessárias.

mnuzzo
fonte
0

Pelo que entendi, o Controller traduz de ações da interface do usuário para ações no nível do aplicativo. Por exemplo, em um videogame, o Controller pode traduzir "moveu o mouse tantos pixels" para "deseja olhar em tal e qual direção. Em um aplicativo CRUD, a tradução pode ser" clicada em tal e tal botão "para "imprima essa coisa", mas o conceito é o mesmo.

David Seiler
fonte
0

Fazemos isso assim, usando Controladores principalmente para manipular e reagir a ações / entradas orientadas pelo usuário (e _Logic para todo o resto, exceto exibição, dados e coisas óbvias sobre _Model):

(1) (resposta, reação - o que o aplicativo da web "faz" em resposta ao usuário) Blog_Controller

-> principal ()

-> handleSubmit_AddNewCustomer ()

-> confirmUser_HasProperAuth ()

(2) (lógica "comercial", o que e como o webapp "pensa") Blog_Logic

-> sanityCheck_AddNewCustomer ()

-> handleUsernameChange ()

-> sendEmail_NotifyRequestedUpdate ()

(3) (visualizações, portais, como o aplicativo da Web "aparece") Blog_View

-> genWelcome ()

-> genForm_AddNewBlogEntry ()

-> genPage_DataEntryForm ()

(4) (apenas objeto de dados, adquirido em _ construct () de cada Blog classe *, usado para manter todos os dados de aplicativos da web / memória juntos como um objeto) Blog_Meta

(5) (camada de dados básica, lê / grava em bancos de dados) Blog_Model

-> saveDataToMemcache ()

-> saveDataToMongo ()

-> saveDataToSql ()

-> loadData ()

Às vezes, ficamos um pouco confusos sobre onde colocar um método, no C ou no L. Mas o Modelo é sólido como uma rocha, claro como cristal e, como todos os dados na memória residem no _Meta, também é um acéfalo. . Nosso maior salto adiante foi a adoção do uso do _Meta, a propósito, já que isso eliminava todo o ruído dos vários objetos _C, _L e _Model, tornando tudo mentalmente fácil de gerenciar, além disso, de uma só vez, nos deu o que estava sendo chamado "Injeção de Dependência", ou uma maneira de repassar um ambiente inteiro junto com todos os dados (cujo bônus é a criação fácil do ambiente de "teste").

FYA
fonte