«1. Обзор

В этом руководстве мы обсудим принцип открытости/закрытости (OCP) как один из SOLID-принципов объектно-ориентированного программирования.

В целом, мы подробно рассмотрим, что это за принцип и как его реализовать при разработке нашего программного обеспечения.

2. Принцип открытости/закрытости

Как следует из названия, этот принцип гласит, что программные объекты должны быть открыты для расширения, но закрыты для модификации. В результате при изменении бизнес-требований объект может быть расширен, но не изменен.

На иллюстрации ниже мы сосредоточимся на том, как интерфейсы являются одним из способов следования OCP.

2.1. Несовместимо

Предположим, мы создаем приложение-калькулятор, которое может иметь несколько операций, таких как сложение и вычитание.

Прежде всего, мы определим интерфейс верхнего уровня – CalculatorOperation:

public interface CalculatorOperation {}

Давайте определим класс Addition, который будет добавлять два числа и реализовывать CalculatorOperation:

public class Addition implements CalculatorOperation {
    private double left;
    private double right;
    private double result = 0.0;

    public Addition(double left, double right) {
        this.left = left;
        this.right = right;
    }

    // getters and setters

}

As На данный момент у нас есть только один класс Addition, поэтому нам нужно определить еще один класс с именем Subtraction:

public class Subtraction implements CalculatorOperation {
    private double left;
    private double right;
    private double result = 0.0;

    public Subtraction(double left, double right) {
        this.left = left;
        this.right = right;
    }

    // getters and setters
}

Давайте теперь определим наш основной класс, который будет выполнять операции нашего калькулятора:

public class Calculator {

    public void calculate(CalculatorOperation operation) {
        if (operation == null) {
            throw new InvalidParameterException("Can not perform operation");
        }

        if (operation instanceof Addition) {
            Addition addition = (Addition) operation;
            addition.setResult(addition.getLeft() + addition.getRight());
        } else if (operation instanceof Subtraction) {
            Subtraction subtraction = (Subtraction) operation;
            subtraction.setResult(subtraction.getLeft() - subtraction.getRight());
        }
    }
}

Хотя это может показаться хорошо, это не хороший пример OCP. Когда появляется новое требование по добавлению функций умножения или деления, у нас нет иного пути, кроме как изменить метод calculate класса Calculator.

Следовательно, мы можем сказать, что этот код не совместим с OCP.

2.2. Совместимость с OCP

Как мы видели, наше приложение-калькулятор еще не совместимо с OCP. Код в методе вычисления будет меняться с каждым входящим запросом на поддержку новой операции. Итак, нам нужно извлечь этот код и поместить его на уровень абстракции.

Одним из решений является делегирование каждой операции соответствующему классу:

public interface CalculatorOperation {
    void perform();
}

В результате класс Addition может реализовать логику сложения двух чисел:

public class Addition implements CalculatorOperation {
    private double left;
    private double right;
    private double result;

    // constructor, getters and setters

    @Override
    public void perform() {
        result = left + right;
    }
}

Аналогично, обновленный класс Subtraction будет имеют схожую логику. И аналогично сложению и вычитанию, в качестве нового запроса на изменение мы могли бы реализовать логику деления:

public class Division implements CalculatorOperation {
    private double left;
    private double right;
    private double result;

    // constructor, getters and setters
    @Override
    public void perform() {
        if (right != 0) {
            result = left / right;
        }
    }
}

И, наконец, нашему классу Calculator не нужно реализовывать новую логику, поскольку мы вводим новые операторы:

public class Calculator {

    public void calculate(CalculatorOperation operation) {
        if (operation == null) {
            throw new InvalidParameterException("Cannot perform operation");
        }
        operation.perform();
    }
}

~ ~~ Таким образом, класс закрыт для модификации, но открыт для расширения.

3. Заключение

В этом уроке мы узнали, что такое OCP по определению, а затем подробно остановились на этом определении. Затем мы увидели пример простого приложения-калькулятора, дизайн которого имел недостатки. Наконец, мы улучшили дизайн, сделав его единым с OCP.

Как всегда, код доступен на GitHub.