Объясни принцип открытости/закрытости (Open-Closed Principle) из SOLID и приведи пример на Java.

Ответ

Принцип открытости/закрытости (Open/Closed Principle, OCP): Программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации.

  • Закрыты для модификации: Существующий, протестированный код не должен изменяться при добавлении новой функциональности.
  • Открыты для расширения: Новая функциональность добавляется путём создания нового кода (наследование, композиция, реализация интерфейсов).

Пример нарушения OCP:

class AreaCalculator {
    public double calculateArea(Object shape) {
        if (shape instanceof Circle) {
            Circle c = (Circle) shape;
            return Math.PI * c.radius * c.radius;
        } else if (shape instanceof Rectangle) {
            Rectangle r = (Rectangle) shape;
            return r.width * r.height;
        }
        // Добавление новой фигуры (Triangle) требует изменения этого метода!
        throw new IllegalArgumentException("Unknown shape");
    }
}

Пример соблюдения OCP:

// 1. Абстракция, открытая для расширения
interface Shape {
    double area();
}

// 2. Конкретные реализации. Их код закрыт для модификации.
class Circle implements Shape {
    private double radius;
    public Circle(double radius) { this.radius = radius; }
    @Override
    public double area() { return Math.PI * radius * radius; }
}

class Rectangle implements Shape {
    private double width, height;
    public Rectangle(double w, double h) { width = w; height = h; }
    @Override
    public double area() { return width * height; }
}

// 3. Класс-клиент. Его код также закрыт для модификации.
class AreaCalculator {
    public double calculateTotalArea(List<Shape> shapes) {
        return shapes.stream().mapToDouble(Shape::area).sum();
    }
}

Как добавить новую фигуру Triangle?

  1. Создаём новый класс Triangle implements Shape с реализацией area().
  2. Никаких изменений в классах Circle, Rectangle или AreaCalculator не требуется.

Выгода: Система становится устойчивой к изменениям, снижается риск внесения ошибок в существующий код.

Ответ 18+ 🔞

Да ты посмотри, что за пиздопроебибна творится в этом коде! Сидит себе программист, как Герасим, под лестницей, и думает: "Му-му, всё норм". А потом бац — нужно треугольник добавить. И что он делает? Правильно, лезет в свой проверенный метод calculateArea и начинает там хуячить if (shape instanceof Triangle).

Вот это и есть, блядь, нарушение принципа открытости-закрытости в чистом виде! Сущность должна быть, как жена друга — закрыта для модификации, но открыта для расширения, ёпта! То есть ты не должен лезть в старый, рабочий код с криками "я ща всё починю!". Ты должен нахуй создать новую сущность и через неё всё сделать.

Смотри, вот пример, от которого волосы дыбом встают:

class AreaCalculator {
    public double calculateArea(Object shape) {
        if (shape instanceof Circle) {
            Circle c = (Circle) shape;
            return Math.PI * c.radius * c.radius;
        } else if (shape instanceof Rectangle) {
            Rectangle r = (Rectangle) shape;
            return r.width * r.height;
        }
        // А тут, сука, когда треугольник понадобится, придётся этот метод пилить!
        throw new IllegalArgumentException("Unknown shape");
    }
}

Представляешь? Каждый раз, когда новая фигура появляется, ты будешь этот метод допиливать. Это ж пиздец, чувак! Один раз напишешь, а потом всю жизнь его ебашить. Терпения ноль ебать!

А теперь смотри, как умные люди делают. Сначала они создают интерфейс — этакую абстрактную херовину, которая говорит: "Ребята, если хотите со мной работать, реализуйте метод area()". И всем похуй, как ты его реализуешь.

interface Shape {
    double area();
}

А дальше — красота! Каждая фигура живёт своей жизнью, как в коммуналке. Круг знает про круги, прямоугольник — про прямоугольники. И никто никому в тарелку не плюёт.

class Circle implements Shape {
    private double radius;
    public Circle(double radius) { this.radius = radius; }
    @Override
    public double area() { return Math.PI * radius * radius; } // Считает по-своему
}

class Rectangle implements Shape {
    private double width, height;
    public Rectangle(double w, double h) { width = w; height = h; }
    @Override
    public double area() { return width * height; } // Считает по-своему
}

И главный калькулятор теперь — просто царь! Он нихуя не знает про конкретные фигуры. Его дело — брать список этих Shape и вызывать у каждого area(). Он закрыт, как швейцарский банк, блядь!

class AreaCalculator {
    public double calculateTotalArea(List<Shape> shapes) {
        return shapes.stream().mapToDouble(Shape::area).sum(); // И всё! Красота!
    }
}

И вот, наступает момент истины. Нам срочно понадобился треугольник. Раньше бы мы, сука, побежали в AreaCalculator и начали там всё ломать. А сейчас? Сейчас мы просто создаём новый класс:

class Triangle implements Shape {
    private double base, height;
    public Triangle(double b, double h) { base = b; height = h; }
    @Override
    public double area() { return 0.5 * base * height; } // Формулу из школы вспомнил!
}

И ВСЁ! Ни один существующий класс — ни Circle, ни Rectangle, ни сам AreaCalculator — даже не узнают, что треугольник появился. Они как жили в своём уютном мире, так и живут. AreaCalculator возьмёт этот треугольник в список и спокойно вызовет у него area(), потому что тот обещал этот метод реализовать.

Вот это и есть принцип, блядь! Система становится устойчивой, как танк. Риск накосячить в старом коде — ноль ебать. Добавляй хоть трапецию, хоть эллипс, хоть манда с ушами, если она реализует интерфейс Shape — всё будет работать.

Вот так-то, а не через жопу instanceof проверки пихать. Чих-пых тебя в сраку, учи матчасть!