Aberto para extensão, fechado para modificação Open-Closed Principle
Domine o segundo princípio do SOLID com exemplos práticos em TypeScript
O código que cresce sem quebrar Open-Closed Principle
Aprenda a criar sistemas que podem ser estendidos sem modificar código existente. Essencial para qualquer dev sênior.
Por que isso é importante
Código mais estável
Adicionar funcionalidades sem quebrar o que já funciona. Zero regressões.
Desenvolvimento mais rápido
Novas features sem mexer em código legado. Deploy com confiança.
Testes isolados
Cada classe testada independentemente. Cobertura real de testes.
Trabalho em equipe
Múltiplos devs trabalhando sem conflitos. Menos merge hell.
O Problema: Código que Quebra
Vamos ver como NÃO fazer. Este código viola o Open-Closed Principle.
❌ Implementação Problemática
// ❌ VIOLANDO o Open-Closed Principle
interface Employee {
name: string;
salary: number;
role: 'developer' | 'designer' | 'manager';
}
// Esta função precisa ser MODIFICADA toda vez que
// adicionamos um novo tipo de funcionário
function calculateBonus(employee: Employee): number {
switch (employee.role) {
case 'developer':
return employee.salary * 0.1; // 10% de bônus
case 'designer':
return employee.salary * 0.05; // 5% de bônus
case 'manager':
return employee.salary * 0.2; // 20% de bônus
default:
return 0;
}
}
// Uso da função
const employees: Employee[] = [
{ name: 'João', salary: 8000, role: 'developer' },
{ name: 'Maria', salary: 6000, role: 'designer' },
{ name: 'Carlos', salary: 12000, role: 'manager' }
];
employees.forEach(emp => {
console.log(`${emp.name}: R$ ${calculateBonus(emp)}`);
});Problemas desta abordagem:
- • Precisa modificar a função para cada novo cargo
- • Risco de quebrar funcionalidades existentes
- • Difícil de testar isoladamente
- • Viola o princípio da responsabilidade única
- • Código cresce de forma descontrolada
Cenário real:
- • Empresa contrata estagiários
- • Precisa adicionar 'intern' no tipo
- • Modifica a função calculateBonus
- • Risco de quebrar testes existentes
- • Deploy com medo
⚠️ Resultado: Cada nova feature é um risco. Código frágil e difícil de manter.
A Solução: Open-Closed Principle
Agora vamos ver como fazer certo. Aberto para extensão, fechado para modificação.
✅ Implementação Correta
1. Classe Base Abstrata
// ✅ Classe base que define o contrato
abstract class Employee {
constructor(
public name: string,
public salary: number
) {}
// Método abstrato que DEVE ser implementado
abstract getBonus(): number;
// Métodos comuns a todos os funcionários
getInfo(): string {
return `${this.name} - Salário: R$ ${this.salary}`;
}
getTotalCompensation(): number {
return this.salary + this.getBonus();
}
}2. Implementações Específicas
// ✅ Cada tipo de funcionário é uma classe
class Developer extends Employee {
constructor(name: string, salary: number, public level: 'junior' | 'pleno' | 'senior') {
super(name, salary);
}
getBonus(): number {
const baseBonus = this.salary * 0.1;
const levelMultiplier = {
'junior': 1,
'pleno': 1.2,
'senior': 1.5
};
export default
return baseBonus * levelMultiplier[this.level];
}
}
class Designer extends Employee {
constructor(name: string, salary: number, public specialty: 'ui' | 'ux' | 'graphic') {
super(name, salary);
}
getBonus(): number {
const baseBonus = this.salary * 0.05;
// Designers UX ganham bônus extra
return this.specialty === 'ux' ? baseBonus * 1.3 : baseBonus;
}
}
class Manager extends Employee {
constructor(name: string, salary: number, public teamSize: number) {
super(name, salary);
}
getBonus(): number {
const baseBonus = this.salary * 0.2;
const teamBonus = this.teamSize * 500; // R$ 500 por pessoa na equipe
return baseBonus + teamBonus;
}
}3. Uso Polimórfico
// ✅ Função que trabalha com qualquer Employee
function calculatePayroll(employees: Employee[]): void {
let totalBonus = 0;
employees.forEach(employee => {
const bonus = employee.getBonus(); // Polimorfismo em ação!
totalBonus += bonus;
console.log(`${employee.getInfo()}`);
console.log(`Bônus: R$ ${bonus}`);
console.log(`Total: R$ ${employee.getTotalCompensation()}`);
console.log('---');
});
console.log(`Total de bônus da empresa: R$ ${totalBonus}`);
}
// Criando funcionários
const employees: Employee[] = [
new Developer('João', 8000, 'senior'),
new Designer('Maria', 6000, 'ux'),
new Manager('Carlos', 12000, 5)
];
// Calculando folha de pagamento
calculatePayroll(employees);✅ Resultado: Código extensível sem modificação. Cada classe tem sua responsabilidade.
Extensão: Adicionando Novo Tipo
Veja como adicionar um estagiário SEM modificar código existente.
🚀 Extensão Sem Modificação
Nova classe: Estagiário
// ✅ NOVA classe - ZERO modificação no código existente
class Intern extends Employee {
constructor(
name: string,
salary: number,
public university: string,
public semester: number
) {
super(name, salary);
}
getBonus(): number {
// Estagiários ganham bônus baseado no semestre
const semesterBonus = this.semester >= 6 ? 300 : 200;
return semesterBonus;
}
// Método específico para estagiários
getAcademicInfo(): string {
return `${this.university} - ${this.semester}º semestre`;
}
}
// ✅ Usando a nova classe
const newEmployees: Employee[] = [
new Developer('João', 8000, 'senior'),
new Designer('Maria', 6000, 'ux'),
new Manager('Carlos', 12000, 5),
new Intern('Ana', 2000, 'UFMG', 7), // NOVO tipo!
new Intern('Pedro', 1800, 'USP', 4) // NOVO tipo!
];
// ✅ A função calculatePayroll funciona SEM modificação!
calculatePayroll(newEmployees);Mais extensões possíveis:
// ✅ Freelancer
class Freelancer extends Employee {
constructor(name: string, salary: number, public projectsCompleted: number) {
super(name, salary);
}
getBonus(): number {
return this.projectsCompleted * 1000; // R$ 1000 por projeto
}
}
// ✅ Consultor
class Consultant extends Employee {
constructor(name: string, salary: number, public expertise: string[]) {
super(name, salary);
}
getBonus(): number {
const expertiseBonus = this.expertise.length * 2000;
return this.salary * 0.15 + expertiseBonus;
}
}
// ✅ CEO
class CEO extends Employee {
constructor(name: string, salary: number, public companyValue: number) {
super(name, salary);
}
getBonus(): number {
return this.companyValue * 0.001; // 0.1% do valor da empresa
}
}🎯 Magia: Adicionamos 5 novos tipos de funcionário sem tocar em UMA linha do código original!
Vantagens Práticas
Por que todo dev sênior usa este princípio.
🧪 Testes Isolados
// ✅ Teste específico para Developer
describe('Developer', () => {
it('should calculate bonus correctly', () => {
const dev = new Developer('João', 8000, 'senior');
expect(dev.getBonus()).toBe(1200); // 8000 * 0.1 * 1.5
});
});
// ✅ Teste específico para Intern
describe('Intern', () => {
it('should give semester bonus', () => {
const intern = new Intern('Ana', 2000, 'UFMG', 7);
expect(intern.getBonus()).toBe(300);
});
});Cada classe testada independentemente. Cobertura real.
🚀 Deploy Seguro
- • Nova feature = nova classe
- • Zero risco de regressão
- • Rollback simples
- • CI/CD confiável
- • Deploy com confiança
👥 Trabalho em Equipe
- • Cada dev trabalha em sua classe
- • Menos conflitos de merge
- • Code review focado
- • Responsabilidades claras
- • Onboarding mais fácil
⚡ Performance
- • Sem switch/case gigantes
- • Polimorfismo é mais rápido
- • Menos condicionais
- • Código mais limpo
- • Menos bugs
Exemplo Avançado: Sistema de Notificações
Aplicação real do Open-Closed Principle em um sistema complexo.
📱 Sistema Extensível de Notificações
Interface base:
// ✅ Interface que define o contrato
interface NotificationChannel {
send(message: string, recipient: string): Promise<boolean>;
validateRecipient(recipient: string): boolean;
getChannelName(): string;
}
// ✅ Classe base com funcionalidades comuns
abstract class BaseNotification implements NotificationChannel {
constructor(protected config: any) {}
abstract send(message: string, recipient: string): Promise<boolean>;
abstract validateRecipient(recipient: string): boolean;
abstract getChannelName(): string;
// Método comum para logging
protected log(action: string, recipient: string, success: boolean): void {
console.log(`[${this.getChannelName()}] ${action} to ${recipient}: ${success ? 'SUCCESS' : 'FAILED'}`);
}
}Implementações específicas:
// ✅ Email
class EmailNotification extends BaseNotification {
async send(message: string, recipient: string): Promise<boolean> {
if (!this.validateRecipient(recipient)) return false;
try {
// Simulação de envio de email
await this.sendEmail(message, recipient);
this.log('Email sent', recipient, true);
return true;
} catch (error) {
this.log('Email failed', recipient, false);
return false;
}
}
validateRecipient(email: string): boolean {
return /^[^s@]+@[^s@]+.[^s@]+$/.test(email);
}
getChannelName(): string { return 'EMAIL'; }
private async sendEmail(message: string, email: string): Promise<void> {
// Integração com serviço de email
console.log(`Sending email to ${email}: ${message}`);
}
}
// ✅ SMS
class SMSNotification extends BaseNotification {
async send(message: string, recipient: string): Promise<boolean> {
if (!this.validateRecipient(recipient)) return false;
try {
await this.sendSMS(message, recipient);
this.log('SMS sent', recipient, true);
return true;
} catch (error) {
this.log('SMS failed', recipient, false);
return false;
}
}
validateRecipient(phone: string): boolean {
return /^+?[1-9]d{1,14}$/.test(phone);
}
getChannelName(): string { return 'SMS'; }
private async sendSMS(message: string, phone: string): Promise<void> {
console.log(`Sending SMS to ${phone}: ${message}`);
}
}
// ✅ Push Notification
class PushNotification extends BaseNotification {
async send(message: string, recipient: string): Promise<boolean> {
if (!this.validateRecipient(recipient)) return false;
try {
await this.sendPush(message, recipient);
this.log('Push sent', recipient, true);
return true;
} catch (error) {
this.log('Push failed', recipient, false);
return false;
}
}
validateRecipient(deviceToken: string): boolean {
return deviceToken.length >= 64; // Token mínimo
}
getChannelName(): string { return 'PUSH'; }
private async sendPush(message: string, token: string): Promise<void> {
console.log(`Sending push to ${token}: ${message}`);
}
}Sistema de notificação:
// ✅ Gerenciador que usa polimorfismo
class NotificationManager {
private channels: Map<string, NotificationChannel> = new Map();
registerChannel(name: string, channel: NotificationChannel): void {
this.channels.set(name, channel);
}
async sendNotification(
channelName: string,
message: string,
recipient: string
): Promise<boolean> {
const channel = this.channels.get(channelName);
if (!channel) {
console.log(`Channel ${channelName} not found`);
return false;
}
return await channel.send(message, recipient);
}
async broadcast(message: string, recipients: Record<string, string>): Promise<void> {
const promises = Object.entries(recipients).map(([channel, recipient]) =>
this.sendNotification(channel, message, recipient)
);
await Promise.all(promises);
}
getAvailableChannels(): string[] {
return Array.from(this.channels.keys());
}
}
// ✅ Uso do sistema
const notificationManager = new NotificationManager();
// Registrando canais
notificationManager.registerChannel('email', new EmailNotification({}));
notificationManager.registerChannel('sms', new SMSNotification({}));
notificationManager.registerChannel('push', new PushNotification({}));
// Enviando notificações
await notificationManager.broadcast('Seu pedido foi aprovado!', {
email: 'user@example.com',
sms: '+5511999999999',
push: 'device_token_here'
});🎯 Resultado: Sistema que pode adicionar WhatsApp, Telegram, Discord sem modificar código existente!
Checklist de Implementação
Como aplicar o Open-Closed Principle no seu código.
✅ Faça Isso
- Use classes abstratas ou interfaces
- Implemente polimorfismo
- Crie uma classe por responsabilidade
- Use dependency injection
- Teste cada classe isoladamente
- Documente contratos claramente
❌ Evite Isso
- Switch/case gigantes
- If/else em cadeia
- Modificar código existente
- Funções com muitas responsabilidades
- Hardcoding de tipos
- Acoplamento forte
🎯 Resumo do Open-Closed Principle
"Software entities should be open for extension, but closed for modification" - Robert C. Martin
Aprofunde seus Conhecimentos em SOLID
Continue explorando nosso conteúdo e aprofunde seus conhecimentos
SOLID Principles TypeScript: Guia Completo dos 5 Princípios
Domine todos os 5 princípios SOLID com exemplos práticos em TypeScript.
Polimorfismo TypeScript: Exemplos Práticos e Aplicações
Aprenda polimorfismo em TypeScript com exemplos práticos e casos de uso reais.
Design Patterns TypeScript: Guia Completo com Exemplos
Explore os principais design patterns implementados em TypeScript.
Explore Mais Sobre Desenvolvimento
Explore Mais Conteúdo
Descubra artigos, tutoriais e cursos que vão acelerar seu crescimento profissional e pessoal
Desenvolvimento
Cursos e Tutoriais
Artigos Mais Populares
Não perca nenhum conteúdo novo
Receba os melhores artigos, tutoriais e cursos sobre desenvolvimento e crescimento profissional.