The SOLID Design Principles
SOLIDThe SOLID Design Principles
June 29th, 2023

SOLID é um acrônimo de cinco princípios de design de software orientado a objetos, compilados por Robert C. Martin. O foco é orientar a estrutura de classes e o fluxo de dependências para facilitar a manutenção de sistemas em crescimento.

Quando o projeto é estruturado no estilo orientado a objetos, os componentes precisam interagir sem gerar acoplamento rígido. Vou destrinchar as cinco regras com pragmatismo.

SRP: Single-responsibility Principle (Responsabilidade Única)

Uma classe tem um motivo para mudar. Se o objeto gerencia a conexão com o banco de dados e também formata um relatório em tela, existe um problema de fronteira. Separe os domínios. No exemplo de código, a classe do carro expõe a locomoção. O ato de dirigir fica isolado na entidade do motorista.

OCP: Open-closed Principle (Aberto-Fechado)

O design permite adicionar comportamento estendendo a base de código, sem modificar arquivos que já funcionam em produção. Você cria classes derivadas ou implementa interfaces em vez de encher a classe base de blocos condicionais. Um carro esportivo estende o contrato de um carro base, injetando o comportamento de aceleração específica.

LSP: Liskov Substitution Principle (Substituição de Liskov)

Tipos derivados são substituíveis por seus tipos base. Se o sistema espera a abstração de um veículo, você injeta um carro popular ou esportivo e a execução flui sem quebrar o contrato. O subtipo respeita a assinatura e as premissas do tipo original.

ISP: Interface Segregation Principle (Segregação de Interface)

Clientes não são forçados a depender de métodos que não utilizam. Interfaces largas geram ruído e acoplamento sem necessidade. É viável dividir os contratos por escopo de uso. Uma interface dita a locomoção e uma interface separada dita o uso do rádio.

DIP: Dependency Inversion Principle (Inversão de Dependência)

Módulos de alto nível não dependem de implementações de baixo nível. Ambos dependem de abstrações. O motorista não é acoplado aos cilindros do motor de uma marca em específico. Ele depende da interface genérica de direção.


Abaixo, estruturei o código em Python usando tipagem estática e classes abstratas (abc) para sair do modelo inicial e ilustrar a engenharia de fato.

from abc import ABC, abstractmethod

# SRP: A interface Veiculo foca na movimentação.
# ISP: Não forçamos métodos como "ligar_radio" na interface base.
class Veiculo(ABC):
    @abstractmethod
    def andar(self) -> None:
        pass

class Carro(Veiculo):
    def andar(self) -> None:
        print("O carro está em movimento.")

# OCP: Criamos um comportamento estendendo a abstração, sem alterar a classe Carro.
class CarroEsportivo(Veiculo):
    def andar(self) -> None:
        print("O carro esportivo está acelerando.")

# ISP: Funcionalidades adjacentes ganham suas próprias interfaces.
class AparelhoDeSom(ABC):
    @abstractmethod
    def ligar_radio(self) -> None:
        pass

class CarroComSom(Veiculo, AparelhoDeSom):
    def andar(self) -> None:
        print("O carro está em movimento.")
    
    def ligar_radio(self) -> None:
        print("O rádio está operando.")

# DIP & SRP: O motorista depende da abstração (Veiculo), não da implementação concreta.
class Motorista:
    def __init__(self, veiculo: Veiculo):
        self.veiculo = veiculo

    def dirigir(self) -> None:
        self.veiculo.andar()

# LSP: Classes que implementam Veiculo operam na função base sem efeitos colaterais.
def iniciar_trajeto(veiculo: Veiculo) -> None:
        veiculo.andar()

# Exemplo de uso
carro_padrao = Carro()
carro_veloz = CarroEsportivo()

motorista_um = Motorista(carro_padrao)
motorista_um.dirigir() 

motorista_dois = Motorista(carro_veloz)
motorista_dois.dirigir()

iniciar_trajeto(carro_padrao)
iniciar_trajeto(carro_veloz)