Este artigo apresenta uma explicação dos princípios SOLID, que é um dos tópicos cobrados no edital do concurso do TSE unificado.
Estruturamos este artigo da seguinte maneira:
Os princípios SOLID são um conjunto de diretrizes que visam melhorar a qualidade do design de software na programação orientada a objetos (POO). Criados por Robert C. Martin (também conhecido como Uncle Bob), esses princípios ajudam desenvolvedores a criar sistemas mais flexíveis, compreensíveis, e fáceis de manter.
Neste artigo, vamos explorar cada um dos cinco princípios SOLID, entendendo sua importância e como aplicá-los com exemplos práticos.
Uma classe deve ter apenas uma razão para mudar, ou seja, ela deve ter apenas uma única responsabilidade.
O Princípio da Responsabilidade Única destaca que cada classe em um sistema deve ser responsável por uma única tarefa ou funcionalidade. Se uma classe faz mais de uma coisa, ela pode se tornar difícil de manter e de testar, pois mudanças em uma funcionalidade podem impactar outras.
Suponha que temos uma classe Relatorio que é responsável por gerar um relatório e também enviá-lo por e-mail.
Essa classe viola o princípio da Responsabilidade Única porque ela tem duas responsabilidades: gerar um relatório e enviá-lo por e-mail. Para aderir a este princípio, devemos separar essas responsabilidades:
Agora, temos duas classes, cada uma com uma responsabilidade única, facilitando a manutenção e a reutilização do código.
Software deve ser aberto para extensão, mas fechado para modificação.
O Princípio Aberto/Fechado sugere que uma classe deve ser fácil de estender para novos comportamentos sem precisar modificar o código existente. Isso pode ser feito através da herança ou da composição, permitindo adicionar novas funcionalidades sem alterar as já existentes, evitando a introdução de bugs.
Considere uma classe que calcula o salário de diferentes tipos de funcionários:
Essa implementação viola o princípio Aberto/Fechado porque toda vez que um novo tipo de funcionário é adicionado, o código da classe precisa ser modificado. Vamos refatorar:
Agora, se precisarmos adicionar novos tipos de funcionários, podemos fazê-lo estendendo a classe Funcionario sem modificar o código existente.
Objetos de uma classe derivada devem poder substituir objetos da classe base sem alterar o comportamento desejado do programa.
O Princípio da Substituição de Liskov afirma que se S é uma subclasse de T, então os objetos do tipo T em um programa podem ser substituídos por objetos de tipo S sem que isso altere as propriedades corretas do programa. Em outras palavras, as subclasses devem manter o comportamento esperado pela superclasse.
Imagine que estamos desenvolvendo um sistema de veículos, onde temos uma classe base Veiculo e duas subclasses: Carro e Bicicleta.
Aqui, temos um problema claro: a classe Bicicleta não deveria herdar de Veiculo porque bicicletas não têm motor. O método ligarMotor não faz sentido para a classe Bicicleta e, ao tentar substituir Veiculo por Bicicleta, podemos causar um comportamento inesperado ou até exceções.
Isso viola o Princípio da Substituição de Liskov, que exige que uma subclasse possa ser substituída por sua superclasse sem quebrar o comportamento do sistema.
Para resolver esse problema, podemos refatorar a hierarquia de classes, separando a funcionalidade de “veículos com motor” e “veículos sem motor”. Vamos introduzir uma interface ou classe abstrata específica para veículos com motor.
Agora, temos uma hierarquia que respeita o Princípio da Substituição de Liskov:
Com essa refatoração, podemos substituir Veiculo por qualquer classe derivada (Carro ou Bicicleta) sem causar comportamentos inesperados ou erros no sistema. Assim, o código se torna mais robusto e alinhado com o princípio da Substituição de Liskov.
Os clientes não devem ser forçados a depender de interfaces que não utilizam.
O Princípio da Segregação de Interfaces sugere que é melhor ter várias interfaces específicas do que uma única interface genérica. Isso evita que as classes implementem métodos que não precisam, reduzindo a complexidade e aumentando a clareza do código.
Vamos considerar uma interface Funcionario:
Essa interface é problemática porque força todos os funcionários a implementarem o método fazerCafe, mesmo que nem todos precisem fazer isso. Podemos dividir essa interface em várias interfaces menores:
Agora, podemos criar classes que implementam apenas as interfaces relevantes:
Isso torna o design mais modular e flexível.
Dependa de abstrações, não de implementações. Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.
O Princípio da Inversão de Dependência sugere que devemos depender de abstrações (como interfaces ou classes abstratas) em vez de implementações concretas. Isso permite que o código seja mais flexível e fácil de modificar, pois podemos trocar uma implementação por outra sem alterar o código que depende dessa abstração.
Vamos começar com um exemplo onde o Princípio da Inversão de Dependência não está sendo seguido:
Aqui, a classe LogService está fortemente acoplada à classe FileLogger. Isso significa que, se quisermos alterar a forma como os logs são armazenados (por exemplo, passar a armazenar logs em um banco de dados), teríamos que modificar a classe LogService. Esse tipo de acoplamento torna o código rígido e difícil de manter.
Para resolver esse problema, aplicamos o Princípio da Inversão de Dependência ao introduzir uma abstração (Logger) e fazer com que LogService dependa dessa abstração em vez de uma implementação concreta como FileLogger.
Essa interface define um contrato que qualquer classe de logger deve implementar.
Agora, FileLogger implementa a interface Logger, o que significa que ele pode ser tratado como um Logger.
Aqui, LogService não depende mais de uma implementação específica (FileLogger). Em vez disso, ela depende da abstração Logger, que pode ser qualquer implementação de logger.
Vamos criar uma outra implementação de Logger para ver como é fácil trocar o comportamento:
Agora, podemos usar DatabaseLogger no lugar de FileLogger sem mudar nada em LogService:
Com essa abordagem, LogService é independente da forma como o log é armazenado. Ele pode usar qualquer implementação de Logger, seja para salvar em arquivo, banco de dados, enviar para a nuvem, etc., simplesmente alterando a implementação passada para ele.
Ao introduzir uma abstração como Logger, tornamos o código mais modular, flexível e aderente ao Princípio da Inversão de Dependência. O LogService agora depende de uma abstração e pode trabalhar com qualquer implementação dessa abstração, facilitando mudanças e mantendo o código mais limpo e sustentável.
A imagem abaixo apresenta um resumo rápido dos princípios SOLID:
Os princípios SOLID são essenciais para o desenvolvimento de software de alta qualidade. Eles ajudam a criar sistemas mais flexíveis, modulares, e de fácil manutenção, o que é fundamental em ambientes de desenvolvimento ágeis e em constante mudança. Ao seguir esses princípios, os desenvolvedores podem evitar problemas comuns no design de software, como código frágil, difícil de testar, e difícil de entender. Portanto, incorporar esses princípios no dia a dia do desenvolvimento pode melhorar significativamente a qualidade do código e a eficiência das equipes de desenvolvimento.
Espero que o conteúdo apresentado tenha sido claro e seja útil para seus estudos e aprovação. Bons estudos!
Quer saber quais serão os próximos concursos?
Confira nossos artigos!
O prefeito eleito de Belém, Igor Normando (MDB), afirmou em entrevista ao Grupo Liberal, que…
Foi publicado no Diário Oficial da União o extrato do novo edital EBSERH (Empresa Brasileira…
Concurso IBAMA: edital até fevereiro de 2025! Foi publicado no Diário Oficial da União o…
Se você está ligad@ aqui no Estratégia Carreiras Jurídicas, sabe a aprovação está no horizonte proximo!…
Se você está ligad@ aqui no Estratégia Carreiras Jurídicas, sabe a aprovação está no horizonte proximo!…
Avançamos em nossa caminhada jurisprudencial. Chegou a hora do Informativo nº 833 do STJ COMENTADO. Pra…