Princípios SOLID em JavaScript: da teoria à prática no Node.js
SOLID — são cinco princípios fundamentais de design orientado a objetos, formulados por Robert Martin (Uncle Bob). Esses princípios ajudam a criar código flexível, sustentável e escalável. Embora muitos associem SOLID a linguagens OOP clássicas (Java, C#), eles são perfeitamente aplicáveis também em JavaScript, especialmente no desenvolvimento com Node.js. Neste artigo, vamos analisar cada princípio com exemplos concretos.
Princípio da Responsabilidade Única (Single Responsibility Principle, SRP)
O SRP afirma: "Uma classe (ou módulo) deve ter apenas um motivo para mudar". Isso significa que cada módulo é responsável por uma tarefa específica.
Exemplo ruim (violação do SRP):
class UserService {
createUser(data) { /* lógica de criação */ }
sendWelcomeEmail(email) { /* envio de email */ }
logActivity(action) { /* registro de log */ }
}
Aqui, uma única classe é responsável por trabalhar com usuários, enviar emails e registrar logs. Se o formato do log mudar, será necessário alterar UserService — mesmo que sua tarefa principal não tenha mudado.
Exemplo bom (respeitando o SRP):
class UserRepository {
createUser(data) { /* apenas interação com o banco de dados */ }
}
class EmailService {
sendWelcomeEmail(email) { /* apenas envio */ }
}
class Logger {
logActivity(action) { /* apenas registro de log */ }
}
class UserService {
constructor(userRepo, emailService, logger) {
this.userRepo = userRepo;
this.emailService = emailService;
this.logger = logger;
}
createUser(data) {
const user = this.userRepo.createUser(data);
this.emailService.sendWelcomeEmail(user.email);
this.logger.logActivity('User created');
return user;
}
}
Agora cada classe é responsável por sua própria área de responsabilidade. Combinando-as em UserService, obtemos flexibilidade e isolamento de mudanças.
Princípio Aberto/Fechado (Open/Closed Principle, OCP)
O OCP 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.
Exemplo ruim (violação do OCP):
class PaymentProcessor {
processPayment(type, amount) {
if (type === 'card') { /* processamento de cartão */ }
else if (type === 'paypal') { /* processamento de PayPal */ }
// Ao adicionar um novo tipo — é necessário modificar este método
}
}
Exemplo bom (respeitando o OCP):
class PaymentStrategy {
pay(amount) { throw new Error('Must be implemented'); }
}
class CardPayment extends PaymentStrategy {
pay(amount) { console.log(`Paid ${amount} via Card`); }
}
class PayPalPayment extends PaymentStrategy {
pay(amount) { console.log(`Paid ${amount} via PayPal`); }
}
class PaymentProcessor {
constructor(strategy) {
this.strategy = strategy;
}
processPayment(amount) {
this.strategy.pay(amount);
}
}
// Uso
const cardPayment = new PaymentProcessor(new CardPayment());
cardPayment.processPayment(100);
const paypalPayment = new PaymentProcessor(new PayPalPayment());
paypalPayment.processPayment(200);
Agora, para adicionar um novo método de pagamento, basta criar uma nova classe que herda de PaymentStrategy e passá-la para PaymentProcessor. O código original não é alterado.
Princípio da Substituição de Liskov (Liskov Substitution Principle, LSP)
O LSP afirma: "Os objetos em um programa devem ser substituíveis por instâncias de seus subtipos sem alterar a correção do programa". Em outras palavras, se você tem uma base
```