Fundamentos e aplicações dos princípios SOLID com Python
O desenvolvimento de software não consiste apenas em escrever algoritmos capazes de produzir resultados corretos. Também envolve a construção de sistemas que possam ser compreendidos, testados, corrigidos e modificados ao longo do tempo. Nesse contexto, os princípios SOLID constituem um conjunto de cinco diretrizes de projeto de software, especialmente associadas à programação orientada a objetos, que procuram melhorar a organização e a capacidade de evolução do código.
Segundo Sommerville (2011), a manutenção e a evolução são atividades fundamentais da engenharia de software, uma vez que os sistemas precisam acompanhar mudanças nos requisitos e nas condições de utilização. De maneira complementar, Pressman e Maxim (2016) destacam que a qualidade do projeto influencia diretamente a facilidade de manutenção e a confiabilidade do produto desenvolvido.
Imagine um sistema informatizado de uma clínica médica. Inicialmente, sua finalidade pode ser apenas cadastrar pacientes e agendar consultas. Entretanto, conforme a clínica cresce, surgem novas necessidades, tal como enviar confirmações de consultas, oferecer diferentes modalidades de atendimento, integrar convênios, gerar relatórios e armazenar informações em bancos de dados.
Se todas essas funcionalidades forem implementadas em uma única classe, qualquer alteração poderá afetar partes aparentemente independentes do sistema. Uma mudança na forma de enviar notificações, por exemplo, poderá exigir modificações no código responsável pelo agendamento.
Os princípios SOLID procuram reduzir problemas dessa natureza por meio de uma distribuição mais adequada das responsabilidades e das dependências entre os componentes.
Ao longo deste artigo, estudaremos os cinco princípios, seus fundamentos e suas implementações em Python 3. Para facilitar a compreensão, todos os exemplos utilizarão um mesmo domínio: o sistema de atendimento de uma clínica médica.
Breve histórico dos princípios SOLID
A origem dos princípios SOLID está relacionada à evolução das técnicas de projeto orientado a objetos, especialmente durante as décadas de 1980 e 1990.
Nesse período, pesquisadores e profissionais da engenharia de software passaram a investigar como organizar classes e objetos de maneira que os sistemas apresentassem menor acoplamento, maior coesão e melhor capacidade de reutilização.
Um marco importante foi o trabalho de Barbara Liskov, que, em 1987, discutiu as propriedades necessárias para que objetos de determinados tipos pudessem substituir objetos de outros tipos sem comprometer a correção dos programas (Liskov, 1987). Essas ideias contribuíram para a formulação do princípio da substituição de Liskov.
Posteriormente, Robert C. Martin sistematizou diversos princípios de projeto orientado a objetos. Em seu artigo Design Principles and Design Patterns, Martin (2000) apresentou diretrizes destinadas a reduzir problemas estruturais, como rigidez, fragilidade e dificuldade de reutilização.
O acrônimo SOLID foi popularizado por Michael Feathers, reunindo cinco princípios associados ao trabalho de Martin. A expressão passou a ser amplamente utilizada na literatura e na prática profissional da engenharia de software. Em Clean Architecture, Martin (2018) aprofunda a discussão e apresenta os princípios SOLID como mecanismos para organizar dependências e estabelecer limites entre componentes de software.
É importante compreender que SOLID não é uma linguagem de programação, um framework ou um padrão arquitetural completo. Trata-se de um conjunto de princípios que orientam decisões de projeto. Sua aplicação depende das características e das necessidades de cada sistema.
Conceitos fundamentais para compreender SOLID
Antes de estudar individualmente os princípios, precisamos compreender três conceitos relacionados à organização de sistemas orientados a objetos.
Coesão
A coesão representa o grau de relacionamento entre as responsabilidades de um módulo ou classe. Uma classe apresenta boa coesão quando suas operações estão concentradas em um propósito bem definido.
Em nosso sistema médico, uma classe responsável por agendar consultas apresenta responsabilidades relacionadas ao processo de agendamento. Se essa mesma classe também gerenciar pagamentos, emitir relatórios financeiros e enviar mensagens, provavelmente estará acumulando responsabilidades distintas.
A busca por componentes coesos favorece a compreensão e a manutenção do software (Pressman; Maxim, 2016).
Acoplamento
O acoplamento representa o grau de dependência entre componentes de um sistema.
Considere uma classe de agendamento que utiliza diretamente uma classe responsável pelo envio de mensagens por e-mail. Caso a clínica decida substituir o e-mail por mensagens de outro serviço, será necessário modificar a classe de agendamento. Ou seja, quanto maior a dependência de detalhes específicos de implementação, maior poderá ser o impacto de determinadas mudanças.
De acordo com Martin (2018), o controle das dependências constitui uma das principais preocupações do projeto de software, pois permite reduzir a propagação de alterações entre componentes.
Abstração
A abstração consiste em representar os elementos essenciais de um problema, deixando de lado detalhes que não são necessários em determinado contexto.
Por exemplo, um sistema de agendamento precisa saber que determinado serviço consegue enviar uma confirmação ao paciente. Entretanto, o processo de agendamento não precisa necessariamente conhecer os detalhes técnicos do protocolo utilizado para transmitir essa mensagem. Essa separação permite que diferentes implementações ofereçam comportamentos equivalentes por meio de contratos compartilhados (Martin, 2018).
Na linguagem Python, podemos representar abstrações utilizando classes abstratas, definidas com recursos do módulo abc, ou contratos estruturais, utilizando Protocol (Python Software Foundation, 2026). Neste artigo, utilizaremos principalmente classes simples e, quando necessário, classes abstratas.
Quais são os cinco princípios SOLID?
A palavra SOLID é formada pelas iniciais de cinco princípios de projeto orientado a objetos. São eles:
| Sigla | Princípio | Ideia central |
|---|---|---|
| S | Single Responsibility Principle (SRP) | Uma classe ou módulo deve ter uma única razão para mudar. |
| O | Open/Closed Principle (OCP) | Componentes devem permitir extensão sem exigir modificações frequentes em seu código existente. |
| L | Liskov Substitution Principle (LSP) | Subtipos devem preservar os contratos esperados dos tipos que substituem. |
| I | Interface Segregation Principle (ISP) | Clientes não devem depender de operações que não utilizam. |
| D | Dependency Inversion Principle (DIP) | Componentes de alto nível devem depender de abstrações, não de detalhes concretos. |
Embora sejam apresentados separadamente, esses princípios se complementam. A aplicação de um deles frequentemente favorece a implementação de outros, especialmente quando se procura construir sistemas flexíveis e testáveis (Martin, 2018).
S — Princípio da Responsabilidade Única (SRP)
O Single Responsibility Principle, ou Princípio da Responsabilidade Única, estabelece que um módulo deve possuir uma única razão para mudar.
Essa definição não significa que uma classe deva possuir apenas um método ou executar apenas uma operação. O princípio está relacionado à concentração de responsabilidades que mudam pelos mesmos motivos e atendem aos mesmos interesses organizacionais (Martin, 2018).
No contexto de uma clínica médica, considere duas responsabilidades distintas: registrar consultas e notificar pacientes. Embora participem do mesmo processo de atendimento, essas atividades podem mudar por razões diferentes.
A clínica pode alterar suas regras de agendamento sem modificar a forma de comunicação com os pacientes. Da mesma maneira, pode substituir o serviço de mensagens sem alterar as regras de agendamento.
Exemplo de implementação inadequada
Considere o código:
class Atendimento:
def agendar(self, paciente, medico):
print(f"Consulta agendada: {paciente} com {medico}")
def enviar_email(self, paciente):
print(f"E-mail enviado para {paciente}")
atendimento = Atendimento()
atendimento.agendar("Ana", "Dr. Carlos")
atendimento.enviar_email("Ana")A classe Atendimento concentra duas responsabilidades: agendamento e comunicação.
O código funciona, mas sua organização pode dificultar futuras modificações. Se a clínica mudar o mecanismo de comunicação, será necessário alterar uma classe que também contém regras de agendamento. Portanto, esse é um exemplo de baixa separação de responsabilidades.
Aplicando o SRP
Uma solução consiste em criar classes especializadas:
class Agendamento:
def agendar(self, paciente, medico):
print(f"Consulta agendada: {paciente} com {medico}")
class Notificacao:
def enviar_email(self, paciente):
print(f"E-mail enviado para {paciente}")
agendamento = Agendamento()
notificacao = Notificacao()
agendamento.agendar("Ana", "Dr. Carlos")
notificacao.enviar_email("Ana")Saída esperada:
Consulta agendada: Ana com Dr. Carlos
E-mail enviado para Ana
Agora, cada classe possui uma responsabilidade mais bem delimitada. A classe Agendamento concentra operações relacionadas ao agendamento de consultas, enquanto Notificacao concentra operações relacionadas à comunicação. Essa organização facilita modificações independentes, além de permitir que as classes sejam testadas separadamente.
O benefício não está simplesmente em aumentar o número de classes, mas em separar responsabilidades que possuem motivos diferentes para mudar.
Quando utilizar o SRP?
O SRP é particularmente útil quando uma classe começa a acumular regras de negócio, persistência de dados, apresentação de informações e integração com serviços externos.
Entretanto, dividir excessivamente classes simples também pode aumentar a complexidade do projeto. A separação deve ser orientada por responsabilidades reais, e não pela quantidade de métodos existentes (Martin, 2018).
O — Princípio Aberto/Fechado (OCP)
O Open/Closed Principle, ou Princípio Aberto/Fechado, estabelece que componentes de software devem estar abertos para extensão, mas fechados para modificação.
A ideia foi apresentada por Bertrand Meyer em seus estudos sobre construção de software orientado a objetos e posteriormente incorporada às discussões de Martin sobre projeto de classes e dependências (Meyer, 1997; Martin, 2000).
Na prática, isso significa organizar componentes para que novos comportamentos possam ser adicionados sem exigir alterações constantes em partes já existentes e testadas. Isso não significa que uma classe nunca possa ser modificada, posi correções de defeitos, mudanças de requisitos e refatorações continuam sendo necessárias.
Exemplo de implementação inadequada
Imagine que uma clínica ofereça consultas particulares e consultas por convênio.
O valor cobrado depende da modalidade:
class Cobranca:
def calcular(self, tipo):
if tipo == "particular":
return 200
elif tipo == "convenio":
return 80
return 0
cobranca = Cobranca()
print(cobranca.calcular("particular"))
print(cobranca.calcular("convenio"))O problema surge quando a clínica começa a oferecer novas modalidades, como teleconsulta ou atendimento domiciliar. Cada nova modalidade exigirá a inclusão de outra condição no método calcular. Dessa forma, uma classe existente precisará ser modificada sempre que uma modalidade for adicionada.
Aplicando o OCP
Podemos representar cada modalidade de cobrança por meio de uma classe que implementa um mesmo contrato:
from abc import ABC, abstractmethod
class ModalidadeConsulta(ABC):
@abstractmethod
def calcular_valor(self):
pass
class ConsultaParticular(ModalidadeConsulta):
def calcular_valor(self):
return 200
class ConsultaConvenio(ModalidadeConsulta):
def calcular_valor(self):
return 80
class Cobranca:
def calcular(self, modalidade):
return modalidade.calcular_valor()
cobranca = Cobranca()
particular = ConsultaParticular()
convenio = ConsultaConvenio()
print(cobranca.calcular(particular))
print(cobranca.calcular(convenio))Saída esperada:
200
80
A classe ModalidadeConsulta define um contrato abstrato. As classes ConsultaParticular e ConsultaConvenio implementam esse contrato conforme suas próprias regras; e a classe Cobranca não precisa conhecer o tipo concreto de consulta. Ela apenas utiliza o método calcular_valor.
Agora, suponha que a clínica passe a oferecer teleconsultas por R$ 120,00.
Podemos adicionar uma nova classe:
class Teleconsulta(ModalidadeConsulta):
def calcular_valor(self):
return 120
teleconsulta = Teleconsulta()
print(cobranca.calcular(teleconsulta))Saída esperada:
120
Observe que não foi necessário modificar a classe Cobranca para incorporar a nova modalidade.
Esse comportamento é possível porque a implementação utiliza polimorfismo, mecanismo pelo qual diferentes objetos podem responder à mesma operação de acordo com suas implementações específicas.
O OCP não exige obrigatoriamente herança. Em Python, também pode ser implementado por meio de composição, funções e outros mecanismos de abstração.
L — Princípio da Substituição de Liskov (LSP)
O Liskov Substitution Principle, ou Princípio da Substituição de Liskov, estabelece que objetos de um subtipo devem poder substituir objetos de seu tipo-base sem comprometer as propriedades e os comportamentos esperados pelo programa.
O princípio está associado aos trabalhos de Barbara Liskov sobre abstração de dados e relações entre tipos. Sua formulação teórica foi aprofundada por Liskov e Wing (1994), que discutiram as condições necessárias para preservar o comportamento durante a substituição de objetos.
Em termos práticos, não basta que uma classe herde de outra; ela precisa respeitar os contratos comportamentais estabelecidos pelo tipo-base. Isso envolve preservar expectativas relacionadas a resultados, condições de utilização e efeitos das operações.
Exemplo de implementação inadequada
Considere que a clínica possui atendimentos presenciais e remotos.
Uma primeira implementação poderia ser:
class Consulta:
def informar_sala(self):
return "Sala 01"
class Teleconsulta(Consulta):
def informar_sala(self):
raise Exception("Teleconsulta não possui sala física")A classe Teleconsulta herda de Consulta, mas não consegue cumprir adequadamente o contrato estabelecido pelo método informar_sala. Se outra parte do sistema espera que toda consulta possua uma sala física, substituir Consulta por Teleconsulta produzirá uma exceção.
O problema não está na existência da exceção em si, mas na incompatibilidade entre o comportamento prometido pela classe-base e o comportamento oferecido pela subclasse.
Aplicando o LSP
Uma solução consiste em definir um contrato que possa ser cumprido por ambas as modalidades:
from abc import ABC, abstractmethod
class Consulta(ABC):
@abstractmethod
def obter_local(self):
pass
class ConsultaPresencial(Consulta):
def obter_local(self):
return "Sala 01"
class Teleconsulta(Consulta):
def obter_local(self):
return "Link de videochamada"
def exibir_local(consulta):
print(consulta.obter_local())
presencial = ConsultaPresencial()
remota = Teleconsulta()
exibir_local(presencial)
exibir_local(remota)Saída esperada:
Sala 01
Link de videochamada
Agora, ambas as classes cumprem o contrato definido por Consulta. A função exibir_local recebe qualquer objeto que implemente corretamente esse contrato, sem precisar identificar sua classe concreta.
A consulta presencial retorna uma localização física, enquanto a teleconsulta retorna um endereço de acesso remoto. Em ambos os casos, o resultado representa uma informação válida sobre onde o atendimento ocorrerá. O aspecto fundamental é que a substituição preserva a expectativa de quem utiliza o objeto.
LSP não significa apenas utilizar herança
Um erro comum consiste em interpretar o LSP como uma regra segundo a qual qualquer subclasse pode substituir sua classe-base simplesmente porque existe uma relação de herança. Na realidade, o princípio trata da compatibilidade comportamental.
Uma subclasse que introduz restrições inesperadas, altera indevidamente o significado dos resultados ou deixa de cumprir operações obrigatórias pode violar o LSP mesmo quando sua estrutura sintática está correta. Portanto, o LSP exige atenção ao significado das operações, e não apenas à organização das classes (Liskov; Wing, 1994).
I — Princípio da Segregação de Interfaces (ISP)
O Interface Segregation Principle, ou Princípio da Segregação de Interfaces, estabelece que clientes não devem ser obrigados a depender de operações que não utilizam.
Segundo Martin (2000), interfaces excessivamente abrangentes podem introduzir dependências desnecessárias, fazendo com que alterações em determinadas funcionalidades afetem componentes que não precisam delas. O princípio recomenda que os contratos sejam organizados conforme as necessidades reais de seus consumidores.
Em linguagens como Java e C#, interfaces são construções explícitas da linguagem. Em Python, podemos representar contratos semelhantes utilizando classes abstratas ou protocolos.
Exemplo de implementação inadequada
Imagine um sistema de clínica que permite realizar consultas e emitir receitas médicas.
Uma implementação inicial poderia ser:
from abc import ABC, abstractmethod
class Funcionario(ABC):
@abstractmethod
def atender(self):
pass
@abstractmethod
def emitir_receita(self):
pass
class Recepcionista(Funcionario):
def atender(self):
print("Paciente recebido na recepção")
def emitir_receita(self):
raise NotImplementedError(
"Recepcionista não emite receitas"
)O problema está no contrato Funcionario. Ele exige que todos os funcionários implementem o método emitir_receita, embora essa atividade não faça parte das atribuições de um recepcionista.
A classe Recepcionista acaba sendo obrigada a implementar uma operação que não deveria oferecer.
Aplicando o ISP
Podemos separar as responsabilidades em contratos menores:
from abc import ABC, abstractmethod
class AtendimentoPaciente(ABC):
@abstractmethod
def atender(self):
pass
class EmissaoReceita(ABC):
@abstractmethod
def emitir_receita(self):
pass
class Recepcionista(AtendimentoPaciente):
def atender(self):
print("Paciente recebido na recepção")
class Medico(AtendimentoPaciente, EmissaoReceita):
def atender(self):
print("Consulta médica realizada")
def emitir_receita(self):
print("Receita médica emitida")
recepcionista = Recepcionista()
medico = Medico()
recepcionista.atender()
medico.atender()
medico.emitir_receita()Saída esperada:
Paciente recebido na recepção
Consulta médica realizada
Receita médica emitida
Agora, existem dois contratos independentes.
AtendimentoPaciente representa a capacidade de atender um paciente, enquanto EmissaoReceita representa a capacidade de emitir uma receita médica. A classe Recepcionista implementa apenas o contrato de atendimento e a classe Medico implementa ambos. Essa organização evita a criação de métodos artificiais e reduz dependências desnecessárias.
É importante observar que o ISP não determina que cada interface deva possuir apenas um método. O objetivo é agrupar operações relacionadas às necessidades de seus consumidores.
D — Princípio da Inversão de Dependência (DIP)
O Dependency Inversion Principle, ou Princípio da Inversão de Dependência, estabelece duas diretrizes fundamentais: módulos de alto nível não devem depender diretamente de módulos de baixo nível, e ambos devem depender de abstrações; além disso, abstrações não devem depender de detalhes concretos de implementação (Martin, 2000).
Um módulo de alto nível contém regras importantes do negócio, enquanto um módulo de baixo nível geralmente realiza operações técnicas, como acessar bancos de dados, enviar mensagens ou comunicar-se com serviços externos.
O DIP procura evitar que regras centrais fiquem excessivamente dependentes desses detalhes. No sistema da clínica, a regra de agendamento de consultas pertence ao domínio do negócio. Já o envio de e-mails representa um detalhe técnico de comunicação.
Exemplo de implementação inadequada
Considere:
class Email:
def enviar(self, paciente):
print(f"E-mail enviado para {paciente}")
class Agendamento:
def __init__(self):
self.notificacao = Email()
def agendar(self, paciente):
print(f"Consulta agendada para {paciente}")
self.notificacao.enviar(paciente)
agendamento = Agendamento()
agendamento.agendar("Ana")O código funciona, mas a classe Agendamento cria diretamente uma instância de Email. Isso significa que a regra de agendamento está vinculada a uma implementação específica de comunicação. Se a clínica decidir utilizar mensagens SMS, será necessário modificar a classe Agendamento.
Aplicando o DIP
Podemos criar uma abstração para os serviços de notificação:
from abc import ABC, abstractmethod
class Notificacao(ABC):
@abstractmethod
def enviar(self, paciente):
pass
class Email(Notificacao):
def enviar(self, paciente):
print(f"E-mail enviado para {paciente}")
class SMS(Notificacao):
def enviar(self, paciente):
print(f"SMS enviado para {paciente}")
class Agendamento:
def __init__(self, notificacao: Notificacao):
self.notificacao = notificacao
def agendar(self, paciente):
print(f"Consulta agendada para {paciente}")
self.notificacao.enviar(paciente)
email = Email()
agendamento = Agendamento(email)
agendamento.agendar("Ana")Saída esperada:
Consulta agendada para Ana
E-mail enviado para Ana
Agora, a classe Agendamento não cria diretamente o serviço de notificação. Ela recebe um objeto que implementa o contrato Notificacao.
Esse mecanismo é conhecido como injeção de dependência: uma dependência necessária ao funcionamento de um componente é fornecida externamente, em vez de ser criada internamente.
Agora podemos substituir o serviço de e-mail por SMS sem modificar a classe Agendamento:
sms = SMS()agendamento = Agendamento(sms)agendamento.agendar("Carlos")A saída esperada é:
Consulta agendada para Carlos
SMS enviado para Carlos
A regra de agendamento permanece inalterada, sendo que apenas o objeto fornecido ao construtor é diferente. Isso facilita a substituição de implementações e a realização de testes automatizados.
DIP e injeção de dependência são a mesma coisa?
A resposta curta é: Não. O DIP é um princípio de organização das dependências, enquanto a injeção de dependência é uma técnica que pode ajudar a implementá-lo.
Também é possível utilizar injeção de dependência sem estabelecer uma abstração adequada. Portanto, fornecer objetos externamente não garante, por si só, que o projeto esteja respeitando o DIP.
Segundo Martin (2018), o objetivo central é preservar as regras de negócio contra dependências desnecessárias de mecanismos e detalhes técnicos.
Como os princípios SOLID se relacionam?
Embora cada princípio tenha uma finalidade específica, eles não devem ser interpretados como regras isoladas. Podemos analisar o sistema da clínica médica para observar essa relação considerando o processo de agendamento de consultas.
O SRP orienta a separação entre agendamento, cobrança e notificação. O OCP permite adicionar novas modalidades de consulta sem modificar constantemente a lógica de cobrança. O LSP garante que implementações especializadas possam substituir seus tipos-base sem comprometer os comportamentos esperados.
Além destes, o ISP contribui para que recepcionistas, médicos e outros componentes dependam apenas das operações necessárias às suas responsabilidades. Finalmente, o DIP permite que o agendamento utilize diferentes serviços de comunicação sem depender diretamente de suas implementações concretas.
Martin (2018) explica que os princípios SOLID contribuem para a construção de estruturas de software cujos componentes podem ser compreendidos, modificados e reutilizados com menor interferência entre si. Entretanto, é sempre bom relembrar que a aplicação desses princípios não elimina a necessidade de planejamento, testes ou análise de requisitos. Eles são instrumentos para orientar decisões de projeto, e não garantias automáticas de qualidade.
SOLID e as particularidades da linguagem Python
A linguagem Python oferece características que permitem aplicar SOLID sem reproduzir necessariamente todas as estruturas tradicionais de linguagens como Java e C#.
Uma dessas características é a tipagem dinâmica, que permite utilizar objetos de diferentes classes desde que ofereçam as operações necessárias. Essa prática está relacionada ao conceito de duck typing (Python Software Foundation, 2026).
Considere o exemplo:
class Email:
def enviar(self, paciente):
print(f"E-mail enviado para {paciente}")
class SMS:
def enviar(self, paciente):
print(f"SMS enviado para {paciente}")
def notificar_paciente(servico, paciente):
servico.enviar(paciente)
notificar_paciente(Email(), "Ana")
notificar_paciente(SMS(), "Carlos")Nesse exemplo, as classes Email e SMS não compartilham uma classe-base explícita. Entretanto, ambas oferecem o método enviar, permitindo que a função notificar_paciente utilize qualquer uma delas. Isso demonstra que, em Python, nem sempre é necessário criar uma hierarquia de herança para obter polimorfismo.
Contudo, existe uma diferença entre compatibilidade estrutural e compatibilidade comportamental. O fato de dois objetos oferecerem métodos com o mesmo nome não garante que seus comportamentos respeitem as mesmas expectativas, conforme discutido no princípio de Liskov (Liskov; Wing, 1994).
Para projetos maiores, os recursos ABC e Protocol podem tornar os contratos mais explícitos, facilitando a documentação e a verificação estática de tipos. A escolha entre essas abordagens depende da complexidade do sistema. Em aplicações pequenas, classes simples e composição podem ser suficientes. Em sistemas maiores, contratos explícitos podem melhorar a organização e a manutenção.
Como identificar violações dos princípios SOLID?
Durante o desenvolvimento, determinados sinais podem indicar problemas de projeto, conforme a tabela a seguir:
| Princípio | Possível sinal de violação |
|---|---|
| SRP | Uma classe precisa ser modificada por motivos relacionados a diferentes responsabilidades. |
| OCP | Toda nova modalidade exige alterações em uma sequência crescente de condicionais. |
| LSP | Uma subclasse precisa lançar exceções inesperadas para operações obrigatórias do tipo-base. |
| ISP | Uma classe implementa métodos vazios ou artificiais apenas para cumprir um contrato. |
| DIP | Regras de negócio criam diretamente objetos de infraestrutura, dificultando sua substituição. |
Esses sinais não constituem provas definitivas de violação. Uma estrutura condicional não representa necessariamente um problema de projeto, por exemplo. Sua adequação depende da estabilidade dos requisitos e da complexidade da aplicação. Da mesma maneira, uma classe com vários métodos pode respeitar perfeitamente o SRP quando todos estão associados à mesma responsabilidade.
Portanto, cabe a você atuar como engenheiro(a) de software para avaliar os benefícios e os custos das decisões arquiteturais.
Conclusão
Os princípios SOLID constituem um conjunto de diretrizes para organizar responsabilidades, controlar dependências e favorecer a evolução de sistemas orientados a objetos.
Seu principal benefício não está em produzir programas com maior quantidade de classes ou interfaces, mas em permitir que modificações sejam realizadas com menor impacto sobre componentes que não deveriam ser afetados. Para o(a) programador(a), compreender SOLID significa desenvolver a capacidade de avaliar não apenas se um código funciona, mas também como ele poderá ser mantido, testado e ampliado no futuro.
Obrigado pela leitura e bons estudos!
Referências
LISKOV, Barbara. Data abstraction and hierarchy. In: ACM Sigplan Conference On Object-oriented Programming Systems, Languages And Applications, 1987. Proceedings […]. New York: ACM, 1987. p. 17-34. DOI: https://doi.org/10.1145/62138.62141.
LISKOV, Barbara H.; WING, Jeannette M. A behavioral notion of subtyping. ACM Transactions on Programming Languages and Systems, New York, v. 16, n. 6, p. 1811-1841, 1994. DOI: https://doi.org/10.1145/197320.197383.
MARTIN, Robert C. Design principles and design patterns. 2000. Disponível em: http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf.
MARTIN, Robert C. Clean architecture: a craftsman’s guide to software structure and design. Boston: Prentice Hall, 2018.
MEYER, Bertrand. Object-oriented software construction. 2. ed. Upper Saddle River: Prentice Hall, 1997.
PRESSMAN, Roger S.; MAXIM, Bruce R. Engenharia de software: uma abordagem profissional. 8. ed. Porto Alegre: AMGH, 2016.
PYTHON SOFTWARE FOUNDATION. abc — Abstract Base Classes. Python 3 documentation. Disponível em: https://docs.python.org/3/library/abc.html. Acesso em: 23 out. 2026.
PYTHON SOFTWARE FOUNDATION. typing — Support for type hints. Python 3 documentation. Disponível em: https://docs.python.org/3/library/typing.html. Acesso em: 23 out. 2026.
SOMMERVILLE, Ian. Engenharia de software. 9. ed. São Paulo: Pearson Prentice Hall, 2011.


