O que são os Princípios SOLID
SOLID são 5 princípios de design orientado a objetos propostos por Robert C. Martin (Uncle Bob). São diretrizes para projetar software com alta manutenibilidade, extensibilidade e testabilidade.
S - Single Responsibility Principle (Princípio da Responsabilidade Única)
O - Open/Closed Principle (Princípio Aberto/Fechado)
L - Liskov Substitution Principle (Princípio da Substituição de Liskov)
I - Interface Segregation Principle (Princípio da Segregação de Interfaces)
D - Dependency Inversion Principle (Princípio da Inversão de Dependência)
1. Princípio da Responsabilidade Única (SRP)
Uma classe deve ter apenas uma responsabilidade. Deve haver apenas uma razão para mudá-la.
Exemplo de Violação
// Exemplo ruim: Classe com múltiplas responsabilidades
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
// Responsabilidade 1: Gerenciamento de dados do usuário
getName() { return this.name; }
setName(name) { this.name = name; }
// Responsabilidade 2: Operações de banco de dados
save() {
db.query('INSERT INTO users ...');
}
// Responsabilidade 3: Envio de e-mail
sendWelcomeEmail() {
emailService.send(this.email, 'Welcome!');
}
// Responsabilidade 4: Serialização JSON
toJSON() {
return JSON.stringify({ name: this.name, email: this.email });
}
}
Exemplo Melhorado
// Bom exemplo: Responsabilidades separadas
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
getName() { return this.name; }
setName(name) { this.name = name; }
}
class UserRepository {
save(user) {
db.query('INSERT INTO users ...');
}
}
class UserNotificationService {
sendWelcomeEmail(user) {
emailService.send(user.email, 'Welcome!');
}
}
class UserSerializer {
toJSON(user) {
return JSON.stringify({ name: user.name, email: user.email });
}
}
2. Princípio Aberto/Fechado (OCP)
Software deve estar aberto para extensão, mas fechado para modificação.
Exemplo de Violação
// Exemplo ruim: Necessidade de modificação a cada novo método de pagamento
class PaymentProcessor {
processPayment(type, amount) {
if (type === 'credit') {
// Processamento de cartão de crédito
} else if (type === 'debit') {
// Processamento de cartão de débito
} else if (type === 'paypal') {
// Processamento PayPal
}
// Adicionar novo método de pagamento requer modificar este método
}
}
Exemplo Melhorado
// Bom exemplo: Aberto para extensão
class PaymentProcessor {
constructor(paymentMethod) {
this.paymentMethod = paymentMethod;
}
processPayment(amount) {
return this.paymentMethod.process(amount);
}
}
// Cada método de pagamento é uma classe independente
class CreditCardPayment {
process(amount) { /* Processamento de cartão de crédito */ }
}
class PayPalPayment {
process(amount) { /* Processamento PayPal */ }
}
// Adicionar novo método de pagamento é possível sem alterar código existente
class CryptoPayment {
process(amount) { /* Processamento de criptomoeda */ }
}
// Uso
const processor = new PaymentProcessor(new CryptoPayment());
processor.processPayment(1000);
3. Princípio da Substituição de Liskov (LSP)
Classes derivadas devem ser substituíveis por suas classes base.
Exemplo de Violação
// Exemplo ruim: Quadrado é um tipo de retângulo, mas comportamento é diferente
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
setWidth(width) { this.width = width; }
setHeight(height) { this.height = height; }
getArea() { return this.width * this.height; }
}
class Square extends Rectangle {
setWidth(width) {
this.width = width;
this.height = width; // Quadrado, então altura também muda
}
setHeight(height) {
this.width = height; // Quadrado, então largura também muda
this.height = height;
}
}
// Código problemático
function increaseRectangleWidth(rect) {
rect.setWidth(rect.width + 1);
// Se Rectangle: area = (width + 1) * height
// Se Square: area = (width + 1) * (width + 1) ← Resultado inesperado
}
Exemplo Melhorado
// Bom exemplo: Usar interface comum
class Shape {
getArea() { throw new Error('Not implemented'); }
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() { return this.width * this.height; }
}
class Square extends Shape {
constructor(side) {
super();
this.side = side;
}
getArea() { return this.side * this.side; }
}
4. Princípio da Segregação de Interfaces (ISP)
Clientes não devem ser forçados a depender de métodos que não usam.
Exemplo de Violação
// Exemplo ruim: Interface muito grande
class Worker {
work() { /* trabalhar */ }
eat() { /* comer */ }
sleep() { /* dormir */ }
}
class Robot extends Worker {
work() { /* trabalhar */ }
eat() { throw new Error('Robots do not eat'); } // Desnecessário
sleep() { throw new Error('Robots do not sleep'); } // Desnecessário
}
Exemplo Melhorado
// Bom exemplo: Interfaces separadas
class Workable {
work() { throw new Error('Not implemented'); }
}
class Eatable {
eat() { throw new Error('Not implemented'); }
}
class Sleepable {
sleep() { throw new Error('Not implemented'); }
}
class Human {
work() { /* trabalhar */ }
eat() { /* comer */ }
sleep() { /* dormir */ }
}
class Robot {
work() { /* trabalhar */ }
// eat() e sleep() não são necessários
}
5. Princípio da Inversão de Dependência (DIP)
Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.
Exemplo de Violação
// Exemplo ruim: Dependência direta de implementação concreta
class UserService {
constructor() {
this.database = new MySQLDatabase(); // Dependência de implementação concreta
}
getUser(id) {
return this.database.query(`SELECT * FROM users WHERE id = ${id}`);
}
}
Exemplo Melhorado
// Bom exemplo: Dependência de abstração (interface)
class Database {
query(sql) { throw new Error('Not implemented'); }
}
class MySQLDatabase extends Database {
query(sql) { /* Implementação MySQL */ }
}
class PostgreSQLDatabase extends Database {
query(sql) { /* Implementação PostgreSQL */ }
}
class UserService {
constructor(database) {
this.database = database; // Dependência de abstração (injeção de dependência)
}
getUser(id) {
return this.database.query(`SELECT * FROM users WHERE id = ${id}`);
}
}
// Uso
const userService = new UserService(new PostgreSQLDatabase());
// Durante testes
const mockDatabase = { query: jest.fn() };
const testService = new UserService(mockDatabase);
Praticando SOLID
Checklist
□ A classe tem apenas uma responsabilidade? (SRP)
□ Novas funcionalidades podem ser adicionadas sem modificar código existente? (OCP)
□ Classes derivadas podem ser usadas no lugar de classes base? (LSP)
□ As interfaces são mínimas? (ISP)
□ Depende de abstrações ao invés de implementações concretas? (DIP)
Equilíbrio na Aplicação
Evite aplicação excessiva:
- Não aplique padrões complexos para problemas simples
- Tenha em mente YAGNI (You Ain't Gonna Need It)
- Refatore gradualmente conforme necessário
Resumo
Os princípios SOLID são diretrizes para projetar software com alta manutenibilidade e extensibilidade. Não é necessário aplicar todos os princípios estritamente o tempo todo, mas mantê-los em mente como critérios de design permite escrever código melhor.
← Voltar para a lista