Sobes.tech
Middle+

Как разбираш принципите SOLID въз основа на опита си?

sobes.tech AI

Отговор от AI

SOLID — набор от пет принципа за обектно-ориентиран дизайн (ООД), които помагат за създаването на гъвкави, разширяеми и лесни за поддръжка системи. Въз основа на опита си, разбирам всеки принцип по следния начин:

  1. S - Single Responsibility Principle (Принцип на единствената отговорност): Всяки клас трябва да има само една причина за промяна. Това означава, че класът трябва да отговаря само за една конкретна функция или част от системата. Това улеснява разбирането, тестването и модифицирането на класа, минимизирайки страничните ефекти при промени.

    class ReportGenerator {
        // Основна отговорност: генериране на отчет
        public void generateReport(Data data) {
            // Логика за генериране на отчет
        }
    
        // Нарушение на SRP: ако този клас също се занимава с запазване на отчета
        // public void saveReport(Report report) { /* ... */ }
    }
    
  2. O - Open/Closed Principle (Принцип на отвореност/затвореност): Програмните същности (класове, модули, функции и т.н.) трябва да бъдат отворени за разширяване, но затворени за модификация. Това се постига чрез използване на абстракции (интерфейси, абстрактни класове) и полиморфизъм. Новият функционал добавя се чрез създаване на нови реализации на съществуващи абстракции, без да се променя техният код.

    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 {
        // Отворен за разширение: можем да добавим нови фигури, без да променяме този метод
        public double calculateTotalArea(Shape[] shapes) {
            double totalArea = 0;
            for (Shape shape : shapes) {
                totalArea += shape.calculateArea();
            }
            return totalArea;
        }
    }
    
  3. L - Liskov Substitution Principle (Принцип на заместимостта на Лисков): Подтиповете трябва да са напълно взаимозаменяеми със своите базови типове. Това означава, че клиентският код, работещ с базовия тип, трябва да работи правилно и с всеки негов подтип, без да трябва да знае за конкретната реализация на подтипа. На практика това често означава, че наследяващите класове не трябва да нарушават контрактите, определени от базовия клас или интерфейс.

    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; // Нарушение на Liskov, ако клиентът очаква независимост
        }
    
        @Override
        public void setHeight(int height) {
            this.width = height;
            this.height = height; // Нарушение на Liskov
        }
    }
    
  4. I - Interface Segregation Principle (Принцип на разделяне на интерфейсите): Клиентите не трябва да зависят от интерфейси, които не използват. По-добре е да има много малки, специфични интерфейси, отколкото един голям "мазен" интерфейс. Това намалява свързаността и позволява на класовете да реализират само функциите, които им трябват.

    interface Worker { // "Мазен" интерфейс
        void work();
        void eat();
        void sleep();
    }
    
    interface Workable { // Разделени интерфейси
        void work();
    }
    
    interface Feedable {
        void eat();
    }
    
    interface Sleepable {
        void sleep();
    }
    
    class HumanWorker implements Workable, Feedable, Sleepable {
        // Реализира всички необходими интерфейси
        // ...
    }
    
    class RobotWorker implements Workable {
        // Реализира само Workable, не зависи от Feedable и Sleepable
        // ...
    }
    
  5. D - Dependency Inversion Principle (Принцип на обръщане на зависимостите):

    • Модулите от по-високо ниво не трябва да зависят от модулите от по-ниско ниво. И двата трябва да зависят от абстракции.
    • Абстракциите не трябва да зависят от детайлите. Детайлите трябва да зависят от абстракциите. Този принцип често се реализира чрез внедряване на зависимости (Dependency Injection), когато зависимостите на класа се предоставят отвън, а не се създават от самия клас. Това намалява свързаността и улеснява тестването.
    // Нарушение на DIP: HighLevelModule директно зависи от LowLevelModule
    class LowLevelModule {
        public void doLowLevelStuff() { /* ... */ }
    }
    
    class HighLevelModule {
        private LowLevelModule lowLevel = new LowLevelModule(); // директна зависимост
    
        public void doHighLevelStuff() {
            lowLevel.doLowLevelStuff();
        }
    }
    
    // Приложение на DIP: и двата зависят от абстракция
    interface Service {
        void execute();
    }
    
    class ConcreteService implements Service {
        @Override
        public void execute() { /* детайли на реализацията */ }
    }
    
    class ClientModule {
        private final Service service; // зависимост от абстракция
    
        // зависимостта се инжектира (например чрез конструктор)
        public ClientModule(Service service) {
            this.service = service;
        }
    
        public void doSomething() {
            service.execute(); // работи с абстракцията
        }
    }