Domine o segundo princípio do SOLID com exemplos práticos em TypeScript
Aprenda a criar sistemas que podem ser estendidos sem modificar código existente. Essencial para qualquer dev sênior.
Adicionar funcionalidades sem quebrar o que já funciona. Zero regressões.
Novas features sem mexer em código legado. Deploy com confiança.
Cada classe testada independentemente. Cobertura real de testes.
Múltiplos devs trabalhando sem conflitos. Menos merge hell.
Vamos ver como NÃO fazer. Este código viola o Open-Closed Principle.
// ❌ 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)}`);
});
⚠️ Resultado: Cada nova feature é um risco. Código frágil e difícil de manter.
Agora vamos ver como fazer certo. Aberto para extensão, fechado para modificação.
// ✅ 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();
}
}
// ✅ 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
};
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;
}
}
// ✅ 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.
Veja como adicionar um estagiário SEM modificar código existente.
// ✅ 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);
// ✅ 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!
Por que todo dev sênior usa este princípio.
// ✅ 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.
Aplicação real do Open-Closed Principle em um sistema complexo.
// ✅ 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'}`);
}
}
// ✅ 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}`);
}
}
// ✅ 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!
Como aplicar o Open-Closed Principle no seu código.
"Software entities should be open for extension, but closed for modification" - Robert C. Martin
Continue explorando nosso conteúdo e aprofunde seus conhecimentos
Domine todos os 5 princípios SOLID com exemplos práticos em TypeScript.
Aprenda polimorfismo em TypeScript com exemplos práticos e casos de uso reais.
Explore os principais design patterns implementados em TypeScript.
Descubra artigos, tutoriais e cursos que vão acelerar seu crescimento profissional e pessoal
Receba os melhores artigos sobre desenvolvimento, negócios e crescimento pessoal
Explorar Blog