Introdução: Por que SOLID é importante no TypeScript
Ao desenvolver em TypeScript, especialmente em projetos grandes, manter a limpeza e flexibilidade do código torna-se criticamente importante. SOLID são cinco princípios fundamentais da programação orientada a objetos que ajudam a criar aplicações escaláveis, facilmente testáveis e sustentáveis. Esses princípios são particularmente relevantes para o TypeScript, que adiciona tipagem estática e interfaces ao JavaScript, fornecendo ferramentas poderosas para sua implementação.
Neste artigo, vamos analisar cada um dos cinco princípios SOLID do ponto de vista do TypeScript, acompanhando a teoria com exemplos práticos de código. Você aprenderá como aplicar esses princípios para melhorar a arquitetura dos seus projetos.
1. Single Responsibility Principle (Princípio da Responsabilidade Única)
Teoria: Cada classe ou módulo deve ter apenas uma razão para mudar. Em outras palavras, a classe deve ser responsável por apenas uma parte da funcionalidade do programa.
Violação do princípio:
// ❌ Ruim: a classe é responsável pelos dados, pela persistência e pela formataçãoclass User { constructor(public name: string, public email: string) {}
saveToDatabase(): void { console.log(`Salvando ${this.name} no BD`); }
generateHTML(): string { return `<div>${this.name} - ${this.email}</div>`; }}Correção aplicando o SRP:
// ✅ Bom: dividimos a responsabilidade em três classesclass User { constructor(public name: string, public email: string) {}}
class UserRepository { save(user: User): void { console.log(`Salvando ${user.name} no BD`); }}
class UserView { render(user: User): string { return `<div>${user.name} - ${user.email}</div>`; }}Agora cada classe tem uma única responsabilidade clara, o que simplifica o teste e a alteração do código.
2. Open-Closed Principle (Princípio do Aberto/Fechado)
Teoria: Entidades de software (classes, módulos, funções) devem estar abertas para extensão, mas fechadas para modificação. Isso significa que você deve ser capaz de adicionar novas funcionalidades sem alterar o código existente.
Violação do princípio:
// ❌ Ruim: ao adicionar um novo tipo de forma, é necessário alterar a classe AreaCalculatorclass AreaCalculator { calculate(shape: any): number { if (shape.type === 'circle') { return Math.PI * shape.radius ** 2; } else if (shape.type === 'rectangle') { return shape.width * shape.height; } return 0; }}Correção aplicando o OCP:
// ✅ Bom: usamos interface e polimorfismointerface Shape { area(): number;}
class Circle implements Shape { constructor(public radius: number) {} area(): number { return Math.PI * this.radius ** 2; }}
class Rectangle implements Shape { constructor(public width: number, public height: number) {} area(): number { return this.width * this.height; }}
class AreaCalculator { calculate(shape: Shape): number { return shape.area(); // Fechado para modificação, aberto para extensão }}Agora, para adicionar uma nova forma (por exemplo, Triangle), basta criar uma nova classe que implemente a interface Shape — sem alterar o AreaCalculator.
3. Liskov Substitution Principle (Princípio da Substituição de Liskov)
Teoria: Os objetos em um programa devem ser substituíveis por instâncias de seus subtipos sem quebrar a correção do programa. Se você tem uma classe base e uma classe derivada, o código que funciona com a classe base deve funcionar corretamente também com a classe derivada.
Violação do princípio:
// ❌ Ruim