MVC e projeto integrador

Separar regras, apresentação e ações, seguindo um movimento completo e os seus testes.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

O herói está na célula 3 e pede um movimento que chegaria à célula 5, fora da arena. O modelo recusa; a vista tem de continuar a mostrar 3. MVC separa a regra dos limites, a interpretação do pedido e a apresentação do resultado.

Model, View e Controller

O Model representa estado e regras do domínio. Mantém invariantes, como ficar dentro da arena. Não precisa de saber se mostramos o herói em Swing ou num terminal.

A View apresenta o estado e recolhe ou encaminha eventos da interface. Traduz valores em texto, formas e componentes. Não decide se um movimento é válido.

O Controller interpreta ações e coordena operações. Um clique, uma tecla ou outro evento pode transformar-se no mesmo pedido ao modelo. Depois a vista apresenta o estado resultante.

Existem variantes de MVC. Nesta página, o controlador atualiza explicitamente a vista. Noutra variante, a vista subscreve mudanças do modelo com Observer. Em ambos os casos, as regras não dependem dos componentes gráficos.

Seguir uma ação completa

Usamos uma arena com cinco células, de zero a quatro, com o herói a começar em dois; pedimos +1, depois +2, depois -1.

A vista mostra o movimento que foi aceite
class Modelo {
    private final int largura;
    private int x;

    Modelo(int largura, int x) {
        if (largura <= 0 || x < 0 || x >= largura) {
            throw new IllegalArgumentException("arena invalida");
        }
        this.largura = largura;
        this.x = x;
    }

    boolean mover(int deslocamento) {
        long destino = (long) x + deslocamento;
        if (destino < 0 || destino >= largura) return false;
        x = (int) destino;
        return true;
    }

    int getX() { return x; }

}

interface Vista {
    void mostrar(int x);
}

class VistaTexto implements Vista {
    public void mostrar(int x) { System.out.println("heroi: " + x); }
}

class Controlador {
    private final Modelo modelo;
    private final Vista vista;

    Controlador(Modelo modelo, Vista vista) {
        this.modelo = modelo;
        this.vista = vista;
    }

    void mover(int deslocamento) {
        modelo.mover(deslocamento);
        vista.mostrar(modelo.getX());
    }

}

public class Main {
    public static void main(String[] args) {
        Modelo modelo = new Modelo(5, 2);
        Controlador controlador = new Controlador(modelo, new VistaTexto());
        controlador.mover(1);
        controlador.mover(2);
        controlador.mover(-1);
    }
}
Resultado

Executa o código para ver a saída e os gráficos.

A saída é heroi: 3, heroi: 3 e heroi: 2. Segue os pedidos consecutivos, sempre a partir do estado aceite anterior:

AntesPedidoDestinoAceite?Mostrado
2+13Sim3
3+25Não3
3-12Sim2

O cálculo usa long para que um deslocamento inteiro muito grande não faça overflow antes de verificar os limites. Só converte para int depois de validar um destino dentro da largura inteira.

O que depende de quê

A montagem, aqui o main, escolhe VistaTexto. O controlador conhece o modelo e a interface Vista. O modelo não importa nenhuma dessas classes de apresentação.

montagem: escolhe e liga os objetos
controlador --> modelo
controlador --> contrato da vista <-- vista concreta

Uma VistaSwing pode implementar o mesmo contrato. Os listeners traduzem eventos em chamadas ao controlador. Liga o botão sem pôr regra na vista:

somar.addActionListener(evento -> controlador.mover(1));

O clique vira mover(1); o controlador pede ao modelo e mostra modelo.getX(). A vista nunca calcula limites. Como os eventos Swing são tratados na EDT, operações curtas do modelo podem correr aí; operações demoradas precisam de uma tarefa separada e de voltar à EDT para apresentar o resultado.

Não escondas uma regra no desenho. Se a vista limita a posição mas o modelo aceita qualquer valor, os testes do modelo e outra vista continuam vulneráveis.

Testar cada comportamento

O modelo pode ser testado sem janela: começa em dois, move para três, recusa cinco e mantém três. Testa também a construção inválida, ambos os limites e deslocamentos extremos. O esperado vem do intervalo da arena.

Para o controlador, passa uma vista controlada e confirma que recebe a posição aceite. O cenário de movimento bloqueado distingue mostrar getX() de mostrar o destino pedido. Concretiza sem janela:

class VistaMemoria implements Vista {
    int ultimo = -1;
    public void mostrar(int x) { ultimo = x; }
}

Usa-a com as classes Modelo e Controlador da lição:

Modelo modelo = new Modelo(5, 4);
VistaMemoria vista = new VistaMemoria();
new Controlador(modelo, vista).mover(1);
assert vista.ultimo == 4;

Se executares este excerto num main, usa java -ea Main para ativar a instrução Java assert; sem -ea, a verificação não corre. Nos testes JUnit, usa assertEquals(4, vista.ultimo), que não depende dessa opção.

VistaMemoria guarda o valor de mostrar(int x) num campo. O modelo recusa 5 e fica em 4; o controlador errado mostraria 5 e este teste falhava. Um teste só com movimentos válidos não reconhece esse erro.

Para a vista concreta, verifica a tradução para texto ou componentes, incluindo os eventos que envia. Não precisas de abrir uma janela para testar a regra dos limites. Um teste completo da aplicação pode depois verificar que uma tecla percorre todas as peças.

Onde entram os padrões

Command pode representar cada ação do utilizador. Strategy pode variar o movimento de um inimigo. State pode controlar Jogar e Pausa. Observer pode avisar vistas de mudanças. Uma fábrica pode escolher objetos na montagem.

Usa-os quando resolvem esse problema no projeto. Não precisas de aplicar todos os padrões nem de contar quantas classes criaste. Na justificação, liga a decisão a uma mudança concreta e explica o que ficou mais fácil de testar ou alterar.

Trabalhar num projeto pequeno

Começa com uma funcionalidade que percorra a aplicação inteira: ler uma ação, validar uma mudança e mostrar o resultado. Guarda esse comportamento e os seus testes antes de acrescentar alternativas.

Divide trabalho por contratos e comportamentos, para evitar que cada pessoa construa uma camada sem nunca testar a ligação. Combina nomes, tipos e invariantes antes de trabalhar em paralelo. Integra cedo e executa o build depois de combinar os branches.

Um relatório técnico deve explicar as escolhas com exemplos do próprio código. Para uma refatoração, mostra a dificuldade inicial, a transformação e a evidência de que o comportamento foi preservado. Para um padrão, identifica participantes e consequências. Um diagrama deve responder a uma pergunta que o texto faz.

As regras e pesos de avaliação pertencem à apresentação da cadeira. Usa as instruções do projeto da tua edição para requisitos, entregas e formato, sem confundir este jogo ilustrativo com um enunciado oficial.

Exercícios

Onde validar uma posição?

A vista Swing verifica limites, mas o modelo aceita qualquer coordenada. Explica por que uma vista de terminal pode quebrar a regra e onde deve ficar a validação. O botão direita deve pedir uma célula ao controlador. Escreve o listener sem calcular limites na vista.

Pista

Uma regra tem de valer independentemente da forma de apresentação.

Mais uma pista
O modelo é o objeto que todos os clientes alteram.
Solução

A validação no Swing só protege esse percurso. O modelo deve recusar mudanças que quebram os limites, para todas as vistas e testes. A interface pode desativar um controlo, mas não substitui a regra no modelo. O listener é direita.addActionListener(evento -> controlador.mover(1)). O controlador pede ao modelo e depois mostra getX(), incluindo quando a tentativa é recusada.

Confere a tua resposta:

  • A regra deve ser protegida pelo modelo.
  • Outra vista pode chamar o modelo sem passar pela validação Swing.
  • O listener chama controlador.mover(1), sem duplicar a validação.

Erros frequentes

Separar classes não basta se a responsabilidade da regra ficou no sítio errado.

Voltar à explicação
Escolher um caso que distingue duas implementações

Um controlador mostra o destino pedido; outro mostra a posição devolvida pelo modelo. Propõe um teste que os distingue, sem abrir uma janela.

Pista
Com movimentos válidos, os dois valores podem coincidir.
Mais uma pista
Usa uma fronteira da arena.
Solução

Numa arena de cinco células, começa em quatro e pede +1. O modelo recusa cinco e fica em quatro. Passa uma vista controlada e verifica mostrar(4). O controlador errado mostra cinco e falha este contrato.

Confere a tua resposta:

  • Usa um movimento que o modelo recusa.
  • Mantém uma posição inicial conhecida.
  • Verifica na vista controlada a posição inicial preservada.

Erros frequentes

Um teste só do caminho válido não distingue pedido e resultado aceite.

Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.