Princípios SOLID em JavaScript: teoria e prática do A ao Z
\n\nO desenvolvimento de software de qualidade exige não apenas o conhecimento da sintaxe da linguagem, mas também a compreensão dos princípios de arquitetura. SOLID são cinco princípios fundamentais de design orientado a objetos que ajudam a criar código flexível, sustentável e escalável. Neste artigo, analisaremos cada princípio SOLID no contexto do JavaScript (Node.js) com exemplos práticos.
\n\nO que é SOLID?
\n\nO acrônimo SOLID foi introduzido por Robert Martin (Uncle Bob) e inclui os seguintes princípios:
\n\n- \n
- S — Single Responsibility Principle (Princípio da Responsabilidade Única) \n
- O — Open/Closed Principle (Princípio Aberto/Fechado) \n
- L — Liskov Substitution Principle (Princípio da Substituição de Liskov) \n
- I — Interface Segregation Principle (Princípio da Segregação de Interface) \n
- D — Dependency Inversion Principle (Princípio da Inversão de Dependência) \n
Esses princípios são universais e aplicáveis a qualquer linguagem de programação, incluindo JavaScript. Vamos examinar cada um deles em detalhes.
\n\n1. Single Responsibility Principle (SRP)
\n\nDefinição: Uma classe ou módulo deve ter apenas um motivo para mudar. Em outras palavras, cada módulo é responsável por uma tarefa específica.
\n\nExemplo ruim: A classe UserService, que é responsável por salvar o usuário, enviar e-mail e gerar relatórios.
// ❌ Violação do SRP\nclass UserService {\n saveUser(user) {\n // Salvar no banco de dados\n console.log('User saved');\n }\n\n sendEmail(user) {\n // Enviar e-mail\n console.log('Email sent to ' + user.email);\n }\n\n generateReport(user) {\n // Gerar relatório\n console.log('Report generated for ' + user.name);\n }\n}\n\nExemplo bom: Dividimos a responsabilidade em classes separadas.
\n\n// ✅ Cumprimento do SRP\nclass UserRepository {\n save(user) {\n console.log('User saved');\n }\n}\n\nclass EmailService {\n send(user) {\n console.log('Email sent to ' + user.email);\n }\n}\n\nclass ReportGenerator {\n generate(user) {\n console.log('Report generated for ' + user.name);\n }\n}\n\n// Uso\nconst user = { name: 'Иван', email: 'ivan@example.com' };\nconst userRepo = new UserRepository();\nconst emailService = new EmailService();\nconst reportGen = new ReportGenerator();\n\nuserRepo.save(user);\nemailService.send(user);\nreportGen.generate(user);\n\nAgora cada classe tem uma única responsabilidade, e as alterações em um módulo não afetam os outros.
\n\n2. Open/Closed Principle (OCP)
\n\nDefinição: Entidades de software (classes, módulos, funções) devem estar abertas para extensão, mas fechadas para modificação.
\n\nExemplo ruim: Uma função que calcula a área de formas usando operadores condicionais.
\n\n// ❌ Violação do OCP\nfunction calculateArea(shape) {\n if (shape.type === 'circle') {\n return Math.PI * shape.radius ** 2;\n } else if (shape.type === 'rectangle') {\n return shape.width * shape.height;\n }\n // Ao adicionar uma nova forma, é necessário modificar a função\n return 0;\n}\n\nExemplo bom: Usamos polimorfismo e herança.
\n\n// ✅ Cumprimento do OCP\nclass Shape {\n area() {\n throw new Error('O método area() deve ser implementado');\n }\n}\n\nclass Circle extends Shape {\n constructor(radius) {\n super();\n this.radius = radius;\n }\n\n area() {\n return Math.PI * this.radius ** 2;\n }\n}\n\nclass Rectangle extends Shape {\n constructor