MVP
Evolução do MVC onde a View é completamente passiva — ela delega toda lógica de apresentação ao Presenter, que a medeia por meio de uma interface, tornando a lógica de UI totalmente testável sem instanciar a tela.
Intenção
Remover toda lógica de apresentação da View e transferi-la ao Presenter, de modo que a View fique tão "burra" que possa ser substituída por um mock nos testes. O Presenter conhece a View apenas por uma interface estreita — e nunca por referências a widgets ou componentes concretos de UI.
MVP surgiu no início dos anos 1990 em trabalhos de Mike Potel (Taligent) e foi popularizado no mundo Android e Windows Forms como solução para o problema que o MVC clássico deixava em aberto: no MVC web, o Controller era fino e delegava ao Model, mas no MVC de aplicações desktop a View tendia a acumular lógica de apresentação — escolha de cor, formatação, habilitação de botões — misturada com eventos de UI.
O MVP resolve isso de forma direta: a View implementa uma interface
(por exemplo IUserView) que expõe apenas operações de
exibição e notificação de eventos. O Presenter recebe essa interface
por injeção e a usa para ler dados do Model e empurrar resultados de
volta à View. A View nunca fala com o Model; o Presenter é o único
mediador.
Problema
Em interfaces gráficas (desktop, mobile, formulários web complexos), o padrão MVC clássico não define claramente quem é responsável pela lógica de apresentação — aquela camada entre "dado bruto" e "pixel na tela": formatação de valores, habilitação condicional de controles, validação de formulário, escolha de mensagem de erro. As consequências práticas são:
- View com código de negócio de apresentação: a View começa a conter condicionais como "se o saldo for negativo, exibir em vermelho" — lógica que pertence a quem conhece o estado, não a quem desenha pixels.
- Testabilidade nula da lógica de UI: para testar se a mensagem de erro aparece corretamente é preciso instanciar a tela inteira — com framework gráfico, thread de UI, evento de clique. O ciclo de feedback é lento e frágil.
- Acoplamento direto View-Model: se a View lê o Model diretamente (como no MVC clássico com Observer), qualquer mudança no modelo pode obrigar mudança na View — os dois evoluem acoplados.
MVP resolve esses três problemas: o Presenter é testável com um mock da interface IView; a View não conhece o Model; e a lógica de apresentação tem um lugar definido — no Presenter.
Estrutura
┌────────────────────────────────────────────────────────────────┐
│ Usuário │
│ (interage com a View) │
└───────────────────────────┬────────────────────────────────────┘
│ evento (clique, input)
▼
┌─────────────────────────────────────────────────────────────┐
│ VIEW │
│ - implementa IView (interface estreita) │
│ - exibe dados que o Presenter empurra │
│ - delega TODOS os eventos ao Presenter │
│ - NÃO conhece o Model │
└───────────────────────────┬─────────────────────────────────┘
│ notifica eventos via IView
▼
┌─────────────────────────────────────────────────────────────┐
│ PRESENTER │
│ - recebe IView por injeção (nunca a classe concreta) │
│ - contém toda a lógica de apresentação │
│ - consulta/atualiza o Model │
│ - empurra resultados chamando métodos da IView │
└───────────────────────────┬─────────────────────────────────┘
│ usa
▼
┌─────────────────────────────────────────────────────────────┐
│ MODEL │
│ - dados + regras de negócio │
│ - completamente ignorante da View e do Presenter │
└─────────────────────────────────────────────────────────────┘
Dependências:
View → IView (implementa a interface)
Presenter → IView (depende da abstração, não da View concreta)
Presenter → Model (lê e atualiza dados)
Model → (ninguém) (não conhece View nem Presenter)
Variante: Passive View
Na variante Passive View (a mais comum e estrita), a View não tem absolutamente nenhuma lógica: ela apenas expõe propriedades (campos de texto, labels, estado de botões) que o Presenter lê e escreve. A View não formata dados, não valida entradas, não toma decisões visuais — tudo isso é responsabilidade do Presenter. O ganho é a testabilidade máxima: o Presenter pode ser testado com um mock trivial de IView.
Variante: Supervising Controller
Na variante Supervising Controller (ou Supervising Presenter), a View é um pouco menos passiva: ela executa binding simples e direto com o Model para casos triviais (como exibir um nome de campo sem transformação), enquanto o Presenter intervém apenas para lógica de apresentação mais complexa. Essa variante é um meio-termo entre MVP e MVVM — ganha simplicidade no Presenter ao custo de um pequeno acoplamento residual da View ao Model.
Como funciona
A interface IView
A peça central do MVP é a interface que a View implementa. Ela deve
ser estreita — apenas os métodos que o Presenter realmente precisa
chamar. Não deve expor widgets, elementos de DOM, componentes de
framework ou qualquer tipo concreto de UI. Deve falar em termos de
dados e intenções: exibirNomeUsuario(nome: string),
mostrarErroEmail(mensagem: string),
habilitarBotaoSalvar(habilitado: boolean).
O Presenter
O Presenter é injetado com a IView (e com o Model ou repositório que precisar). Ele registra-se como listener dos eventos da View — ou a View chama diretamente métodos do Presenter quando o usuário age. Quando um evento ocorre, o Presenter executa a lógica, consulta o Model se necessário e chama os métodos da IView para atualizar a tela. O Presenter nunca instancia a View; ele a recebe pronta.
Exemplo de código
// ── MODEL — dados e regras de negócio ────────────────────────
class UsuarioModel {
buscarPorId(id: string): { nome: string; email: string } | null {
// acessa repositório; a View nunca chama isto diretamente
return { nome: 'Ana', email: 'ana@exemplo.com' };
}
}
// ── IVIEW — interface estreita que a View implementa ─────────
// Só operações de exibição e notificação — nenhum widget concreto.
interface IPerfilView {
exibirNome(nome: string): void;
exibirEmail(email: string): void;
mostrarErro(mensagem: string): void;
habilitarEdicao(habilitado: boolean): void;
}
// ── PRESENTER — toda a lógica de apresentação ─────────────────
class PerfilPresenter {
constructor(
private readonly view: IPerfilView,
private readonly model: UsuarioModel,
) {}
carregarPerfil(id: string): void {
const usuario = this.model.buscarPorId(id);
if (!usuario) {
this.view.mostrarErro('Usuário não encontrado.');
this.view.habilitarEdicao(false);
return;
}
this.view.exibirNome(usuario.nome);
this.view.exibirEmail(usuario.email);
this.view.habilitarEdicao(true);
}
}
// ── VIEW CONCRETA — implementa IPerfilView ────────────────────
// Pode ser DOM, React, Android Activity, Windows Form etc.
class PerfilViewDOM implements IPerfilView {
exibirNome(nome: string): void {
document.getElementById('nome')!.textContent = nome;
}
exibirEmail(email: string): void {
document.getElementById('email')!.textContent = email;
}
mostrarErro(mensagem: string): void {
document.getElementById('erro')!.textContent = mensagem;
}
habilitarEdicao(habilitado: boolean): void {
(document.getElementById('btn-editar') as HTMLButtonElement)
.disabled = !habilitado;
}
}
// ── MOCK DE VIEW — usado nos testes unitários do Presenter ────
class MockPerfilView implements IPerfilView {
nomExibido = '';
erroExibido = '';
edicaoHabilitada = false;
exibirNome(nome: string): void { this.nomExibido = nome; }
exibirEmail(_email: string): void { /* ignorado no teste */ }
mostrarErro(msg: string): void { this.erroExibido = msg; }
habilitarEdicao(h: boolean): void { this.edicaoHabilitada = h; }
}
<?php
// ── MODEL — dados e regras de negócio ────────────────────────
class UsuarioModel
{
public function buscarPorId(string $id): ?array
{
// acessa repositório; a View nunca chama isto diretamente
return ['nome' => 'Ana', 'email' => 'ana@exemplo.com'];
}
}
// ── IVIEW — interface estreita que a View implementa ─────────
// Só operações de exibição e notificação — nenhum widget concreto.
interface IPerfilView
{
public function exibirNome(string $nome): void;
public function exibirEmail(string $email): void;
public function mostrarErro(string $mensagem): void;
public function habilitarEdicao(bool $habilitado): void;
}
// ── PRESENTER — toda a lógica de apresentação ─────────────────
class PerfilPresenter
{
public function __construct(
private readonly IPerfilView $view,
private readonly UsuarioModel $model,
) {}
public function carregarPerfil(string $id): void
{
$usuario = $this->model->buscarPorId($id);
if ($usuario === null) {
$this->view->mostrarErro('Usuário não encontrado.');
$this->view->habilitarEdicao(false);
return;
}
$this->view->exibirNome($usuario['nome']);
$this->view->exibirEmail($usuario['email']);
$this->view->habilitarEdicao(true);
}
}
// ── VIEW CONCRETA — implementa IPerfilView ────────────────────
// Pode ser um template Blade, um componente Livewire, etc.
class PerfilViewHtml implements IPerfilView
{
private string $nome = '';
private string $email = '';
private string $erro = '';
private bool $edicaoHabilitada = false;
public function exibirNome(string $nome): void { $this->nome = $nome; }
public function exibirEmail(string $email): void { $this->email = $email; }
public function mostrarErro(string $mensagem): void { $this->erro = $mensagem; }
public function habilitarEdicao(bool $habilitado): void { $this->edicaoHabilitada = $habilitado; }
public function render(): string
{
// Toda saída HTML gerada aqui — sem lógica de apresentação
$nomeEsc = htmlspecialchars($this->nome, ENT_QUOTES, 'UTF-8');
$emailEsc = htmlspecialchars($this->email, ENT_QUOTES, 'UTF-8');
$erroEsc = htmlspecialchars($this->erro, ENT_QUOTES, 'UTF-8');
$disabled = $this->edicaoHabilitada ? '' : 'disabled';
return "<p>{$nomeEsc}</p><p>{$emailEsc}</p>"
. "<p class='erro'>{$erroEsc}</p>"
. "<button {$disabled}>Editar</button>";
}
}
Note que o PerfilPresenter pode ser testado unitariamente
instanciando um MockPerfilView e um UsuarioModel
com dados fictícios — sem framework gráfico, sem DOM, sem HTTP. Esse é
o ganho central do MVP.
Quando usar
- Aplicações desktop ou mobile com lógica de apresentação complexa: formulários com validação condicional, habilitação dinâmica de controles, formatação dependente de regras — são cenários onde o Presenter tem trabalho real a fazer e a testabilidade sem UI física é um ganho imediato.
- Quando a testabilidade da lógica de UI é prioritária: MVP permite testar todo o comportamento visual (o que aparece, quando aparece, em que estado) sem instanciar um único widget ou abrir um browser. Para aplicações Android legadas ou Windows Forms, era a principal estratégia de teste.
- Quando a View pode ser trocada sem alterar a lógica: ao definir IView como contrato, é possível ter uma View gráfica para produção e uma View de console para testes ou scripts — o Presenter não muda.
Quando evitar
- Frameworks modernos com data binding nativo (Vue, Angular, React + state management): nesses ambientes o binding declarativo elimina a necessidade de o Presenter "empurrar" dados explicitamente para a View. O padrão adequado passa a ser MVVM ou arquiteturas baseadas em estado reativo (Flux, Redux, Signals).
- Views com lógica mínima ou nenhuma: se a View é apenas um template estático que renderiza dados sem lógica condicional relevante, a camada Presenter é desnecessária — um MVC simples é mais produtivo.
-
Equipes sem familiaridade com inversão de dependência:
a IView é um contrato que precisa ser mantido consistente entre
View e Presenter. Se a equipe não estiver disciplinada, a interface
vaza detalhes concretos de UI (parâmetros do tipo
HTMLElementouButton), destruindo a testabilidade que o MVP promete.
Prós e contras
Prós
- Presenter completamente testável sem instanciar a View real — basta um mock da IView.
- View passiva: pode ser trocada (DOM, Android, console) sem alterar o Presenter.
- Separação clara entre lógica de apresentação (Presenter) e renderização (View).
- Model totalmente isolado da camada de apresentação — a View nunca o toca diretamente.
- Contratos explícitos via IView facilitam o trabalho paralelo entre quem desenvolve a UI e quem implementa a lógica.
Contras
- Mais código de infraestrutura: para cada View é necessária uma interface IView, um Presenter e testes do Presenter — o MVP multiplica artefatos mesmo para telas simples.
- Risco de Presenter gordo: sem disciplina, toda a lógica de aplicação migra para o Presenter, que passa a orquestrar casos de uso, validar regras de negócio e formatar dados — misturando responsabilidades que pertencem ao Model.
- Manutenção da IView: cada mudança na tela que exige um novo dado empurrado pelo Presenter requer atualização da interface, da View concreta e do mock de testes.
- Menos adequado para UIs reativas modernas: o fluxo "Presenter chama view.exibir()" é imperativo e não se encaixa naturalmente em arquiteturas baseadas em estado observável.
Armadilhas comuns
1. Presenter gordo
A armadilha mais frequente no MVP. O Presenter começa focado em apresentação, mas vai acumulando chamadas a serviços externos, regras de validação de negócio, orquestração de múltiplos casos de uso e até transformações de dados que pertencem ao Model. O resultado é um Presenter com centenas de linhas que mistura apresentação, aplicação e domínio. A regra prática é clara: o Presenter deve delegar ao Model (ou a serviços de aplicação) tudo que não é decisão de como exibir um dado na tela.
Regra prática: se o Presenter contém cálculos de negócio, consultas a banco ou chamadas a APIs externas diretamente (sem passar por serviços ou repositórios), ele está gordo. Extraia essas responsabilidades para o Model ou para uma camada de aplicação.
2. IView que vaza widgets concretos
Se a interface IView define métodos como
getEmailTextField(): TextField ou recebe parâmetros do
tipo Button ou HTMLInputElement, o Presenter
passa a depender do framework gráfico — e a testabilidade colapsa.
A IView deve falar apenas em termos de dados e intenções:
obterEmailDigitado(): string,
habilitarBotaoConfirmar(habilitado: boolean): void.
Nenhum tipo de UI concreta deve aparecer nos parâmetros ou retornos
da interface.
3. View que bypassa o Presenter
Em aplicações com acesso fácil ao repositório ou ao Model (por injeção de dependência global ou singleton), é tentador a View chamar o Model diretamente "só para ler um dado rápido". Esse bypass destrói a invariante central do MVP: a View não conhece o Model. Uma vez introduzida, essa exceção tende a se multiplicar, degradando o padrão para um MVC mal estruturado.
4. Confundir MVP com MVC renomeado
No MVC web clássico (Laravel, Rails, Django), o Controller recebe a requisição, chama o Model e passa dados para a View renderizar. A View pode até ler dados do Model diretamente em alguns frameworks. No MVP, a View nunca conhece o Model — essa é a distinção definidora. Se o seu "Presenter" ainda passa o objeto Model inteiro para a View renderizar, você tem MVC com o Controller renomeado, não MVP.
Arquiteturas e padrões relacionados
- MVC
- MVVM (Model-View-ViewModel)
- Observer
- Humble Object (em breve)
O MVC é o precursor direto do MVP. A diferença central é o papel da View: no MVC clássico, a View pode observar o Model diretamente (via padrão Observer) e se atualizar sem passar pelo Controller; no MVP, a View nunca toca o Model — o Presenter é o único elo entre os dois. MVP pode ser visto como um MVC onde o Controller foi renomeado para Presenter e a View foi tornada completamente passiva.
O Observer é o mecanismo que o MVP usa para que a View notifique o Presenter de eventos de usuário: a View dispara eventos (clique, submit, digitação) e o Presenter é o observador registrado. Alternativamente, a View chama diretamente métodos do Presenter — mas mesmo nesse caso o padrão Observer está implícito no modelo de eventos da plataforma (DOM events, Android listeners).
O MVVM pode ser entendido como uma evolução do MVP para ambientes com data binding nativo. A diferença-chave: no MVP o Presenter empurra dados para a View chamando métodos da IView explicitamente; no MVVM o ViewModel expõe estado observável e o binding sincroniza a View automaticamente — o ViewModel não chama a View, ele não a conhece. Em UIs reativas modernas (Vue, Angular, SwiftUI, Compose), MVVM é mais natural que MVP.
O Humble Object é um padrão de testabilidade descrito por Gerard Meszaros que formaliza exatamente o que o MVP pratica: separar o código difícil de testar (a View com framework gráfico) do código fácil de testar (o Presenter com lógica pura). A View no MVP é literalmente um Humble Object — ela não tem lógica própria para testar.