Sobes.tech
Middle+

How do you understand the principles of SOLID based on your experience?

sobes.tech AI

Answer from AI

SOLID is a set of five principles of object-oriented design (OOD) that help create flexible, extendable, and maintainable systems. Based on my experience, I understand each principle as follows:

  1. S - Single Responsibility Principle (SRP): Each class should have only one reason to change. This means that a class should only be responsible for one specific function or part of the system. This simplifies understanding, testing, and modifying the class, minimizing side effects when changes are made.

    class ReportGenerator {
        // Main responsibility: report generation
        public void generateReport(Data data) {
            // Logic for report generation
        }
    
        // Violation of SRP: if this class also handles saving reports
        // public void saveReport(Report report) { /* ... */ }
    }
    
  2. O - Open/Closed Principle (OCP): Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This is achieved through the use of abstractions (interfaces, abstract classes) and polymorphism. New functionality is added by creating new implementations of existing abstractions without changing their code.

    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 {
        // Open for extension: we can add new shapes without changing this method
        public double calculateTotalArea(Shape[] shapes) {
            double totalArea = 0;
            for (Shape shape : shapes) {
                totalArea += shape.calculateArea();
            }
            return totalArea;
        }
    }
    
  3. L - Liskov Substitution Principle (LSP): Subtypes must be fully substitutable for their base types. This means that client code working with the base type should work correctly with any of its subtypes without knowing the specific implementation of the subtype. In practice, this often means that subclasses should not violate contracts defined by the base class or 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; // Violation of LSP if client expects independence
        }
    
        @Override
        public void setHeight(int height) {
            this.width = height;
            this.height = height; // Violation of LSP
        }
    }
    // Correct approach with LSP often requires rethinking the hierarchy
    // For example, having separate classes for Rectangle and Square
    
  4. I - Interface Segregation Principle (ISP): Clients should not depend on interfaces they do not use. It is better to have many small, specific interfaces than one large, "fat" interface. This reduces coupling and allows classes to implement only the functions they actually need.

    interface Worker { // "Fat" interface
        void work();
        void eat();
        void sleep();
    }
    
    interface Workable { // Segregated interfaces
        void work();
    }
    
    interface Feedable {
        void eat();
    }
    
    interface Sleepable {
        void sleep();
    }
    
    class HumanWorker implements Workable, Feedable, Sleepable {
        // Implements all necessary interfaces
        // ...
    }
    
    class RobotWorker implements Workable {
        // Implements only Workable, does not depend on Feedable and Sleepable
        // ...
    }
    
  5. D - Dependency Inversion Principle (DIP):

    • High-level modules should not depend on low-level modules. Both should depend on abstractions.
    • Abstractions should not depend on details. Details should depend on abstractions. This principle is often implemented through dependency injection, where class dependencies are provided from outside rather than created by the class itself. This reduces coupling and facilitates testing.
    // Violation of DIP: HighLevelModule directly depends on LowLevelModule
    class LowLevelModule {
        public void doLowLevelStuff() { /* ... */ }
    }
    
    class HighLevelModule {
        private LowLevelModule lowLevel = new LowLevelModule(); // Direct dependency
    
        public void doHighLevelStuff() {
            lowLevel.doLowLevelStuff();
        }
    }
    
    // Applying DIP: both depend on an abstraction
    interface Service {
        void execute();
    }
    
    class ConcreteService implements Service {
        @Override
        public void execute() { /* Implementation details */ }
    }
    
    class ClientModule {
        private final Service service; // Dependency on abstraction
    
        // Dependency is injected (e.g., via constructor)
        public ClientModule(Service service) {
            this.service = service;
        }
    
        public void doSomething() {
            service.execute(); // Works with abstraction
        }
    }
    

Adhering to SOLID principles, in my experience, leads to cleaner, more extendable, and maintainable code, which is critical for the long-term development of software products.

How do you understand the principles of SOLID based… - sobes.tech