Introdução aos princípios SOLID em JavaScript
SOLID é um acrônimo de cinco princípios fundamentais da programação orientada a objetos, formulados por Robert Martin (Uncle Bob). Apesar de JavaScript não ser originalmente uma linguagem OOP estritamente tipada, o JavaScript moderno (ES6+) com classes, módulos e TypeScript utiliza ativamente esses princípios para criar aplicações escaláveis e sustentáveis, especialmente no ambiente Node.js.
Neste artigo, vamos analisar cada um dos cinco princípios SOLID com exemplos práticos de código em JavaScript. Compreender esses princípios ajudará você a escrever código limpo, flexível e resistente a mudanças.
1. Single Responsibility Principle (SRP) — Princípio da Responsabilidade Única
O princípio afirma: uma classe ou módulo deve ter apenas um motivo para ser alterado. Em outras palavras, cada módulo é responsável por uma funcionalidade específica.
Violando o SRP
Considere a classe UserService, que simultaneamente gerencia usuários, envia e-mails e registra ações:
class UserService { constructor() { this.users = []; }
addUser(name) { this.users.push({ name }); console.log('User added'); // Logging this.sendEmail(name); // Envio de email }
sendEmail(name) { console.log(`Email sent to ${name}`); }
getUsers() { return this.users; }}Aqui, a classe tem três responsabilidades: gerenciamento de dados, logging e envio de notificações. Alterar a lógica de logging ou envio de e-mail resultará em modificações na classe UserService.
Seguindo o SRP
Vamos separar as responsabilidades em classes distintas:
class Logger { log(message) { console.log(`[LOG]: ${message}`); }}
class EmailService { send(name) { console.log(`Email sent to ${name}`); }}
class UserService { constructor(logger, emailService) { this.users = []; this.logger = logger; this.emailService = emailService; }
addUser(name) { this.users.push({ name }); this.logger.log(`User ${name} added`); this.emailService.send(name); }
getUsers() { return this.users; }}Agora, cada classe é responsável por sua própria tarefa, e alterações em um serviço não afetam os outros.
2. Open-Closed Principle (OCP) — Princípio Aberto/Fechado
O princípio afirma: entidades de software devem estar abertas para extensão, mas fechadas para modificação. Ou seja, devemos ser capazes de adicionar novas funcionalidades sem alterar o código existente.
Violando o OCP
Suponha que temos uma função para calcular a área de formas:
function calculateArea(shape) { if (shape.type === 'circle') { return Math.PI * shape.radius ** 2; } else if (shape.type === 'rectangle') { return shape.width * shape.height; } // Ao adicionar uma nova forma, será necessário modificar esta função}Seguindo o OCP
Usamos polimorfismo através de classes:
class Shape { area() { throw new Error('Method "area()" must be implemented'); }}
class Circle extends Shape { constructor(radius) { super(); this.radius = radius; } area() { return Math.PI * this.radius ** 2; }}
class Rectangle extends Shape { constructor(width, height) { super(); this.width = width; this.height = height; } area() { return this.width * this.height; }}
// Adicionamos uma nova forma sem modificar o código existenteclass Triangle extends Shape { constructor(base, height) { super(); this.base = base; this.height = height; } area() { return (this.base * this.height) / 2; }}
function calculateArea