Sobes.tech
Middle+

Como você entende os princípios SOLID com base na sua experiência?

sobes.tech IA

Resposta da IA

SOLID é um conjunto de cinco princípios de design orientado a objetos (OO), que ajudam a criar sistemas flexíveis, escaláveis e fáceis de manter. Com base na minha experiência, entendo cada princípio assim:

  1. S - Princípio da Responsabilidade Única (SRP): Cada classe deve ter uma única razão para mudar. Isso significa que a classe deve ser responsável por uma única função ou parte do sistema. Isso facilita a compreensão, teste e modificação da classe, minimizando efeitos colaterais.

    class ReportGenerator {
        // Responsabilidade principal: geração de relatório
        public void generateReport(Data data) {
            // Lógica de geração de relatório
        }
    
        // Violação do SRP: se esta classe também se encarregar de salvar relatórios
        // public void saveReport(Report report) { /* ... */ }
    }
    
  2. O - Princípio Aberto/Fechado (OCP): As entidades de software (classes, módulos, funções, etc.) devem estar abertas para extensão, mas fechadas para modificação. Isso é alcançado usando abstrações (interfaces, classes abstratas) e polimorfismo. Funcionalidades novas são adicionadas criando novas implementações de abstrações existentes, sem alterar seu código.

    interface Shape {
        double calculateArea();
    }
    
    class Circle implements Shape {
        private double radius;
        public Circle(double radius) { this.radius = radius; }
        @Override
        public double calculateArea() { return Math.PI * radius * radius; }
    }
    
    class Square implements Shape {
        private double side;
        public Square(double side) { this.side = side; }
        @Override
        public double calculateArea() { return side * side; }
    }
    
    class AreaCalculator {
        // Aberto para extensão: podemos adicionar novas figuras, sem mudar este método
        public double calculateTotalArea(Shape[] shapes) {
            double totalArea = 0;
            for (Shape shape : shapes) {
                totalArea += shape.calculateArea();
            }
            return totalArea;
        }
    }
    
  3. L - Princípio de Substituição de Liskov (LSP): Os subtipos devem ser completamente intercambiáveis com seus tipos base. O código cliente que trabalha com o tipo base deve funcionar corretamente com qualquer subtipo sem precisar conhecer a implementação concreta. Na prática, isso significa que as classes derivadas não devem violar contratos definidos pela classe base ou interface.

    class Rectangle {
        protected int width;
        protected int height;
    
        public void setWidth(int width) { this.width = width; }
        public void setHeight(int height) { this.height = height; }
    
        public int getArea() { return width * height; }
    }
    
    class SquareLSP extends Rectangle {
        @Override
        public void setWidth(int width) {
            this.width = width;
            this.height = width; // Violação do LSP, se o cliente espera independência
        }
    
        @Override
        public void setHeight(int height) {
            this.width = height;
            this.height = height; // Violação do LSP
        }
    }
    
  4. I - Princípio de Segregação de Interfaces (ISP): Os clientes não devem depender de interfaces que não usam. É melhor ter muitas interfaces pequenas e específicas do que uma grande e 'gorda'. Isso reduz o acoplamento e permite que as classes implementem apenas as funções que realmente precisam.

    interface Worker { // Interface 'gorda'
        void work();
        void eat();
        void sleep();
    }
    
    interface Workable { // Interfaces segregadas
        void work();
    }
    
    interface Feedable {
        void eat();
    }
    
    interface Sleepable {
        void sleep();
    }
    
    class HumanWorker implements Workable, Feedable, Sleepable {
        // Implementa todas as interfaces necessárias
        // ...
    }
    
    class RobotWorker implements Workable {
        // Implementa apenas Workable, sem depender de Feedable e Sleepable
        // ...
    }
    
  5. D - Princípio de Inversão de Dependências (DIP):

    • Os módulos de nível superior não devem depender de módulos de nível inferior. Ambos devem depender de abstrações.
    • As abstrações não devem depender de detalhes. Os detalhes devem depender de abstrações. Este princípio é frequentemente realizado por injeção de dependências, onde as dependências de uma classe são fornecidas de fora em vez de criadas internamente. Isso reduz o acoplamento e facilita os testes.
    // Violação do DIP: HighLevelModule depende diretamente de LowLevelModule
    class LowLevelModule {
        public void doLowLevelStuff() { /* ... */ }
    }
    
    class HighLevelModule {
        private LowLevelModule lowLevel = new LowLevelModule(); // Dependência direta
    
        public void doHighLevelStuff() {
            lowLevel.doLowLevelStuff();
        }
    }
    
    // Aplicando DIP: ambos dependem de uma abstração
    interface Service {
        void execute();
    }
    
    class ConcreteService implements Service {
        @Override
        public void execute() { /* Detalhes de implementação */ }
    }
    
    class ClientModule {
        private final Service service; // Dependência da abstração
    
        // A dependência é injetada (por exemplo, via construtor)
        public ClientModule(Service service) {
            this.service = service;
        }
    
        public void doSomething() {
            service.execute(); // Trabalha com a abstração
        }
    }
    

Seguindo esses princípios, meu código se torna mais limpo, mais fácil de estender e manter, o que é fundamental para o desenvolvimento de longo prazo de produtos de software.