No mundo do desenvolvimento de software, a qualidade e a sustentabilidade do design são cruciais. Aqui, os princípios SOLID desempenham um papel fundamental. Desenvolvidos por Robert C. Martin, esses princípios orientam os desenvolvedores na criação de software que é fácil de manter, escalar e compreender. Vamos explorar cada um deles com exemplos práticos em TypeScript.
1. Single Responsibility Principle (Princípio da Responsabilidade Única)
Conceito: Uma classe deve ter apenas uma razão para mudar, significando que deve ter apenas uma tarefa ou responsabilidade.
Exemplos:
// Ruim: a classe lida com detalhes de usuário e log
class User {
private name: string;
constructor(name: string) {
this.name = name;
}
saveUser() {
console.log('Usuário salvo no banco de dados');
}
logUserDetails() {
console.log(`Usuário: ${this.name}`);
}
}
// Bom: separar responsabilidades
class User {
private name: string;
constructor(name: string) {
this.name = name;
}
saveUser() {
console.log('Usuário salvo no banco de dados');
}
}
class UserLogger {
logUser(user: User) {
console.log(`Usuário: ${user.getName()}`);
}
}
Explicação: Ao separar a lógica de log do usuário da sua classe User
, seguimos o princípio da responsabilidade única. Isso torna as classes mais fáceis de entender, testar e manter.
2. Open/Closed Principle (Princípio Aberto/Fechado)
Conceito: Entidades de software (classes, módulos, funções) devem estar abertas para extensão, mas fechadas para modificação.
Exemplos:
// Ruim: a função de desenho deve ser modificada para cada nova forma
class Circle {}
class Square {}
function drawShape(shape: Circle | Square) {
if (shape instanceof Circle) {
// desenha círculo
} else if (shape instanceof Square) {
// desenha quadrado
}
}
// Bom: usando polimorfismo
interface Shape {
draw(): void;
}
class Circle implements Shape {
draw() {
// desenha círculo
}
}
class Square implements Shape {
draw() {
// desenha quadrado
}
}
function drawShape(shape: Shape) {
shape.draw();
}
Explicação: Este princípio é exemplificado permitindo que novas formas sejam adicionadas (extensão) sem alterar a função drawShape
(modificação). Promove um design flexível e sustentável.
3. Liskov Substitution Principle (Princípio da Substituição de Liskov)
Conceito: Objetos de uma classe devem ser substituíveis por objetos de suas subclasses sem quebrar a aplicação.
Exemplo:
// Bom: substituição sem alterar o comportamento
class Bird {
fly() {
// implementação do voo
}
}
class Duck extends Bird {}
function makeBirdFly(bird: Bird) {
bird.fly();
}
const duck = new Duck();
makeBirdFly(duck); // Funciona corretamente
Explicação: Este princípio garante que as subclasses mantenham o comportamento esperado das classes base, promovendo a reutilização e correção do código.
4. Interface Segregation Principle (Princípio da Segregação de Interface)
Conceito: Nenhuma classe deve ser forçada a implementar interfaces que não vai usar.
Exemplo:
// Ruim: interface muito grande
interface Worker {
work(): void;
eat(): void;
}
class HumanWorker implements Worker {
work() { /*...*/ }
eat() { /*...*/ }
}
class RobotWorker implements Worker {
work() { /*...*/ }
eat() { // Robôs não comem, mas a implementação é obrigatória
}
}
// Bom: interfaces segregadas
interface Worker {
work(): void;
}
interface Eater {
eat(): void;
}
class HumanWorker implements Worker, Eater {
work() { /*...*/ }
eat() { /*...*/ }
}
class RobotWorker implements Worker {
work() { /*...*/ }
}
Explicação: Este princípio promove a criação de interfaces específicas e relevantes, evitando a implementação desnecessária de métodos, que pode levar a um código confuso e de difícil manutenção.
5. Dependency Inversion Principle (Princípio da Inversão de Dependência)
Conceito: Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.
Exemplo:
// Ruim: alto nível depende diretamente de baixo nível
class LightBulb {
turnOn() { /*...*/ }
turnOff() { /*...*/ }
}
class ElectricPowerSwitch {
private bulb: LightBulb;
constructor(bulb: LightBulb) {
this.bulb = bulb;
}
operate() {
if (this.bulb.isOn()) {
this.bulb.turnOff();
} else {
this.bulb.turnOn();
}
}
}
// Bom: depende de abstrações
interface Switchable {
turnOn(): void;
turnOff(): void;
}
class LightBulb implements Switchable {
turnOn() { /*...*/ }
turnOff() { /*...*/ }
}
class ElectricPowerSwitch {
private device: Switchable;
constructor(device: Switchable) {
this.device = device;
}
operate() {
// ...
}
}
Explicação: Este princípio minimiza a dependência direta entre módulos de software, promovendo um acoplamento mais fraco e um design mais flexível.
Conclusão
Os princípios SOLID são fundamentais para qualquer desenvolvedor que busque criar aplicações robustas, sustentáveis e fáceis de manter. Ao aplicar ess
es princípios, o software se torna mais modular, o que facilita a realização de mudanças, a implementação de novas funcionalidades e a manutenção do sistema como um todo. Em TypeScript, esses princípios podem ser implementados de maneira clara e eficaz, contribuindo significativamente para a qualidade do design de software.
Top comments (4)
Muito bom! 👏
Obrigado Luiz 😊
sempre bom ter um resuminho de SOLID na mão, valeu meu querido
Excelente explicação dos princípios SOLID, deixou de fato evidente a real necessidade de se utilizar cada um.