<h2>MVC: O Fundamento</h2>
<p>Model-View-Controller é o padrão mais tradicional em arquitetura de frontend, separando responsabilidades em três camadas. O Model gerencia dados, a View exibe informações e o Controller orquestra a comunicação entre elas.</p>
<p>O fluxo é bidirecional: usuário interage com a View, Controller captura a ação, atualiza o Model, e o Model notifica a View para re-renderizar. Esse ciclo, apesar de simples, pode gerar acoplamento se não for bem implementado. Vamos a um exemplo prático em JavaScript puro:</p>
<pre><code class="language-javascript">// Model
class UserModel {
constructor() {
this.users = [];
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
addUser(name) {
this.users.push({ id: Date.now(), name });
this.notifyObservers();
}
notifyObservers() {
this.observers.forEach(obs => obs.update(this.users));
}
}
// View
class UserView {
constructor(elementId) {
this.element = document.getElementById(elementId);
}
render(users) {
this.element.innerHTML = users
.map(user => <div>${user.name}</div>)
.join('');
}
update(users) {
this.render(users);
}
}
// Controller
class UserController {
constructor(model, view) {
this.model = model;
this.view = view;
this.model.addObserver(this.view);
}
handleAddUser(name) {
this.model.addUser(name);
}
}
// Uso
const model = new UserModel();
const view = new UserView('app');
const controller = new UserController(model, view);
controller.handleAddUser('João');</code></pre>
<p>O MVC funciona bem em projetos pequenos e médios, mas começa a mostrar limitações em aplicações complexas onde múltiplas views precisam sincronizar com o mesmo modelo.</p>
<h2>MVVM: Binding Automático</h2>
<p>Model-View-ViewModel introduz uma camada intermediária que elimina a comunicação direta entre View e Model. O ViewModel expõe dados reativos que a View observa automaticamente, criando um two-way binding.</p>
<p>Diferentemente do MVC, você não precisa notificar manualmente a View. Frameworks como Vue.js implementam MVVM nativamente. O ViewModel contém toda a lógica de apresentação, deixando a View completamente declarativa. Aqui está um exemplo com Vue 3:</p>
<pre><code class="language-javascript">// ViewModel + View combinados
<template>
<div>
<input v-model="userInput" placeholder="Nome" />
<button @click="addUser">Adicionar</button>
<div v-for="user in users" :key="user.id">
{{ user.name }}
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
// Model
const users = ref([]);
const userInput = ref('');
// ViewModel (aqui mesmo)
const addUser = () => {
if (userInput.value.trim()) {
users.value.push({
id: Date.now(),
name: userInput.value
});
userInput.value = '';
}
};
</script></code></pre>
<p>O MVVM reduz boilerplate e torna o código mais intuitivo para quem trabalha com interfaces reativas. Porém, em aplicações com estado global complexo, ele pode criar ViewModels inchados e difíceis de testar.</p>
<h2>Flux: Unidirecionalidade Garantida</h2>
<p>Flux é uma arquitetura, não um padrão, que resolve o problema de estado compartilhado através de um fluxo de dados unidirecional: Actions → Dispatcher → Stores → Views. Quando uma View precisa mudar estado, dispara uma Action, nunca modifica diretamente o Store.</p>
<p>Essa rigidez torna o código previsível e facilita debug. Redux popularizou essa ideia, adicionando imutabilidade e uma store centralizada. Veja um exemplo funcional:</p>
<pre><code class="language-javascript">// Action Creator
const ADD_USER = 'ADD_USER';
function addUserAction(name) {
return { type: ADD_USER, payload: name };
}
// Reducer (implementa a lógica)
function usersReducer(state = [], action) {
switch(action.type) {
case ADD_USER:
return [...state, {
id: Date.now(),
name: action.payload
}];
default:
return state;
}
}
// Store (Redux simplificado)
class SimpleStore {
constructor(reducer) {
this.reducer = reducer;
this.state = reducer(undefined, {});
this.listeners = [];
}
dispatch(action) {
this.state = this.reducer(this.state, action);
this.listeners.forEach(listener => listener(this.state));
}
subscribe(listener) {
this.listeners.push(listener);
}
getState() {
return this.state;
}
}
// Uso
const store = new SimpleStore(usersReducer);
store.subscribe(state => {
console.log('Estado atualizado:', state);
});
store.dispatch(addUserAction('Maria'));</code></pre>
<p>Flux é ideal para aplicações médias e grandes onde múltiplos componentes compartilham estado. A desvantagem é verbosidade inicial e curva de aprendizado.</p>
<h2>Event-Driven Design: Desacoplamento Total</h2>
<p>Em arquiteturas event-driven, componentes comunicam-se através de eventos, sem conhecimento direto uns dos outros. Um Publisher emite eventos, Subscribers reagem. Isso permite escalabilidade e flexibilidade máximas.</p>
<p>Diferencia-se de Flux porque não há ordem fixa: eventos podem ser capturados por múltiplos listeners, e novos listeners podem ser adicionados sem modificar o sistema. É especialmente poderoso em aplicações complexas e microsserviços frontend. Exemplo prático:</p>
<pre><code class="language-javascript">// Event Emitter pattern
class EventBus {
constructor() {
this.events = {};
}
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
emit(eventName, data) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => callback(data));
}
}
}
// Componentes desacoplados
const bus = new EventBus();
// Componente que cria usuário
class UserCreator {
constructor(bus) {
this.bus = bus;
}
create(name) {
this.bus.emit('user:created', { name, id: Date.now() });
}
}
// Componente que reage
class UserLogger {
constructor(bus) {
bus.on('user:created', (user) => {
console.log('Novo usuário:', user.name);
});
}
}
// Uso
const creator = new UserCreator(bus);
const logger = new UserLogger(bus);
creator.create('Pedro'); // Log: "Novo usuário: Pedro"</code></pre>
<p>Event-driven é menos estruturado que Flux, exigindo disciplina para não virar "callback hell". Ideal para aplicações modulares onde componentes aparecem e desaparecem dinamicamente.</p>
<h2>Conclusão</h2>
<p>Cada arquitetura resolve problemas específicos: <strong>MVC</strong> é direto e tradicional para projetos simples; <strong>MVVM</strong> oferece reatividade elegante em frameworks modernos; <strong>Flux</strong> garante previsibilidade em aplicações complexas com estado global; <strong>Event-Driven</strong> maximiza desacoplamento e flexibilidade. Na prática, você combina essas abordagens: um app pode usar Flux para estado global, MVVM em componentes e event-driven para cross-cutting concerns. O segredo é escolher a ferramenta certa para cada camada.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://redux.js.org/" target="_blank" rel="noopener noreferrer">Redux Documentation</a></li>
<li><a href="https://vuejs.org/guide/extras/reactivity-in-depth.html" target="_blank" rel="noopener noreferrer">Vue.js Reactivity Guide</a></li>
<li><a href="https://www.patterns.dev/posts/event-driven-architecture/" target="_blank" rel="noopener noreferrer">Event-Driven Architecture Pattern</a></li>
<li><a href="https://www.geeksforgeeks.org/difference-between-mvc-and-mvvm/" target="_blank" rel="noopener noreferrer">MVC vs MVVM - Differences and When to Use Each</a></li>
<li><a href="https://www.oreilly.com/library/view/software-architecture-patterns/9781491971437/" target="_blank" rel="noopener noreferrer">Software Architecture Patterns - O'Reilly</a></li>
</ul>