🏛️ Capítulo 17: Padrões Arquiteturais de UI: MVC, MVP e MVVM
Módulo 10: Paradigmas de Programação & Padrões de Projeto • Bloco 5: Arquitetura e Enterprise Patterns
🏛️ Separar o que o Usuário Vê do que o Sistema Sabe: Toda interface gráfica mistura três preocupações que mudam por motivos diferentes — os dados e regras do domínio, a aparência da tela e a reação às ações do usuário. Quando elas vivem no mesmo arquivo, qualquer ajuste visual ameaça a regra de negócio e nada pode ser testado sem abrir a tela. Neste capítulo comparamos as três grandes famílias de padrões de apresentação — MVC, MVP e MVVM — e mostramos por que frameworks modernos (Android Jetpack, SwiftUI, Vue, WPF, Flutter) convergiram para variações do MVVM.
🗺️ Mapa Conceitual do Capítulo
flowchart TD
UI_ARCH["Padrões de Apresentação e UI"]
UI_ARCH --> MVC["1. MVC (Model-View-Controller)<br/>• Reenskaug, Xerox PARC (1979)<br/>• Controller recebe a entrada e atualiza o Model<br/>• View observa o Model"]
UI_ARCH --> MVP["2. MVP (Model-View-Presenter)<br/>• Taligent/IBM (anos 1990), Fowler: Passive View<br/>• View passiva atrás de uma interface<br/>• Presenter 100% testável sem GUI"]
UI_ARCH --> MVVM["3. MVVM (Model-View-ViewModel)<br/>• Microsoft WPF (2005)<br/>• ViewModel expõe estado observável<br/>• Data Binding elimina código de atualização"]
MVC -->|"evolução: View sem lógica"| MVP
MVP -->|"evolução: binding automático"| MVVM
style UI_ARCH fill:#ede7f6,stroke:#512da8,stroke-width:2px
style MVC fill:#e8f5e9,stroke:#4caf50,stroke-width:1.5px
style MVP fill:#fff3e0,stroke:#ff9800,stroke-width:1.5px
style MVVM fill:#e1f5fe,stroke:#03a9f4,stroke-width:1.5px
🧩 1. O Ponto de Partida: Separated Presentation
Os três padrões compartilham a mesma premissa, que Martin Fowler chamou de Separated Presentation: o Model (entidades, regras, persistência) não deve saber que existe uma interface gráfica. Isso permite:
- Trocar a tela (web → mobile → CLI) sem reescrever regras de negócio;
- Testar o domínio com testes unitários rápidos;
- Ter vários “rostos” para os mesmos dados (um painel e um relatório lendo o mesmo Model).
O que muda entre MVC, MVP e MVVM é quem conversa com quem do lado da apresentação — e isso define o quanto a lógica de tela pode ser testada.
🏛️ 2. O Padrão MVC (Model-View-Controller)
Criado por Trygve Reenskaug na Xerox PARC (1979), para o Smalltalk-80:
- Model: estado do domínio e regras de negócio puras (validações, cálculos, persistência).
- View: renderiza os dados do Model para o usuário.
- Controller: captura a entrada (clique, tecla, requisição HTTP), aciona o Model e escolhe a View.
flowchart LR
U(("Usuário")) -->|"ação"| C["Controller"]
C -->|"atualiza"| M["Model"]
M -.->|"notifica mudança (Observer)"| V["View"]
V -->|"lê dados"| M
C -->|"seleciona"| V
V -->|"exibe"| U
2.1 Os Dois “MVCs” que Você Vai Encontrar
- MVC clássico (desktop): a View observa o Model (padrão Observer do Capítulo 14) e se redesenha sozinha quando ele muda.
- MVC web (Model 2): usado por Rails, Django, Spring MVC e Laravel. Não há observação contínua: a requisição HTTP chega ao Controller, que consulta o Model e entrega os dados a um template (a View), gerando HTML uma vez por requisição.
⚠️ Armadilha — Massive View Controller: no iOS/UIKit, o
UIViewControlleracumulava papéis de View e Controller ao mesmo tempo, virando classes de milhares de linhas impossíveis de testar. Esse sintoma motivou a migração para MVP e MVVM.
🎭 3. O Padrão MVP (Model-View-Presenter)
No MVP, a View torna-se passiva: ela apenas repassa eventos ao Presenter e executa ordens de exibição. Toda decisão de apresentação sai da View:
classDiagram
class ICarrinhoView {
<<interface>>
+codigo_digitado() str
+mostrar_total(texto)
+mostrar_erro(texto)
+habilitar_botao_aplicar(bool)
}
class TelaAndroid
class ViewEspia
class CarrinhoPresenter {
-view: ICarrinhoView
-catalogo: CatalogoCupons
+ao_digitar_codigo()
+ao_clicar_aplicar()
}
ICarrinhoView <|.. TelaAndroid : produção
ICarrinhoView <|.. ViewEspia : testes
CarrinhoPresenter --> ICarrinhoView
CarrinhoPresenter --> CatalogoCupons
3.1 Por que a Interface da View é o Segredo
Como o Presenter depende de ICarrinhoView (uma abstração — DIP do Capítulo 6), podemos substituí-la por uma View espiã nos testes e verificar exatamente quais comandos o Presenter enviou:
view = ViewEspia()
presenter = CarrinhoPresenter(view, CatalogoCupons(), subtotal=150.0)
view.codigo = "bemvindo10"
presenter.ao_clicar_aplicar()
assert view.chamadas[-1] == "total='Total: R$ 135.00 (10% off)'"
3.2 Custos do MVP
- A relação Presenter ↔ View é 1:1 e bidirecional: o Presenter precisa chamar cada método de atualização manualmente.
- Telas complexas geram interfaces de View enormes (dezenas de métodos
mostrar_x,esconder_y). - O ciclo de vida (rotação de tela no Android, por exemplo) exige cuidado para não manter referência a uma View destruída.
⚡ 4. O Padrão MVVM (Model-View-ViewModel)
Proposto por John Gossman (Microsoft, 2005) para o WPF, o MVVM resolve o principal custo do MVP: o código de atualização manual. O ViewModel não conhece a View — ele apenas expõe estado observável e comandos. Um mecanismo de Data Binding mantém a tela sincronizada.
flowchart LR
V["View<br/>(XAML, Compose, template Vue)"] <-->|"Data Binding"| VM["ViewModel<br/>• propriedades observáveis<br/>• comandos"]
VM -->|"chama"| M["Model / Repositório"]
M -->|"retorna dados"| VM
4.1 Binding Unidirecional vs. Bidirecional
- One-way (ViewModel → View): a tela reflete o estado (
StateFlow+collectAsStateno Android,@Publishedno SwiftUI). - Two-way (View ↔ ViewModel): a digitação atualiza o estado e o estado atualiza o campo (
v-modelno Vue,{Binding Mode=TwoWay}no WPF). - Estado derivado: propriedades como
pode_converterse recalculam automaticamente quando as de origem mudam — sem nenhumifna View.
4.2 MVVM e Arquitetura Unidirecional (UDF)
Frameworks declarativos modernos (Jetpack Compose, SwiftUI, React, Flutter) combinam o ViewModel com o fluxo unidirecional: eventos sobem da tela para o ViewModel, estado desce do ViewModel para a tela. Variações como MVI (Model-View-Intent) e Redux levam essa ideia ao extremo, com um único objeto de estado imutável.
⚠️ Armadilha: um ViewModel que importa classes de UI (
Context,Button,document) deixou de ser ViewModel. Se ele não roda num teste unitário puro, a separação foi quebrada.
📊 5. Matriz Comparativa Sistemática: MVC vs. MVP vs. MVVM
| Dimensão de Análise | MVC | MVP | MVVM |
|---|---|---|---|
| Quem recebe a entrada do usuário | Controller | View, que delega ao Presenter | View, ligada a comandos do ViewModel |
| Como a View é atualizada | Observa o Model (desktop) ou é renderizada por requisição (web) | Presenter chama métodos da interface da View | Data Binding automático |
| Presentation conhece a View? | Controller conhece a View | Sim, via interface (1:1) | Não |
| Testabilidade da lógica de tela | Média | Alta (View falsa) | Alta (sem nenhuma View) |
| Boilerplate | Baixo | Alto (interfaces de View) | Médio (infraestrutura de binding) |
| Ecossistemas típicos | Rails, Django, Spring MVC, Laravel | Android clássico, WinForms, GWT | WPF, Jetpack, SwiftUI, Vue, Angular |