Рубрики
Без рубрики

ТВЕРДОЕ ТЕЛО: Принцип Инверсии Зависимостей

Это последняя статья о принципах SOLID и, на мой взгляд, самая важная…. С тегами java, ооп, программирование, архитектура.

Это последняя статья о принципах SOLID и, на мой взгляд, самая важная. Инверсия зависимостей является основой для одной из наиболее полезных функций, реализованных в настоящее время во многих фреймворках, а именно для внедрения зависимостей. Этот НАДЕЖНЫЙ принцип придает вашей архитектуре необходимую гибкость для достижения разделения задач между уровнями, и это концепция, которую должен знать каждый разработчик.

Определение

Роберт К. Мартин определяет DI следующим образом:

A. Модули высокого уровня не должны зависеть от модулей низкого уровня. И то, и другое должно зависеть от абстракций. B. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Модули высокого и низкого уровня:

Чтобы полностью понять принцип инверсии зависимостей, мы должны сначала понять концепцию модулей высокого и низкого уровня.

Программный модуль – это блок кодирования, содержащий одну или несколько подпрограмм. Обычно модуль несет только одну ответственность во всей системе. Вы создаете систему, используя несколько модулей, которые могут быть сгруппированы в разные уровни. Давайте приведем наглядный пример этого:

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

Когда мы говорим о модулях высокого уровня, мы говорим о модулях, которые непосредственно используются или создаются на уровне представления. В нашем примере есть только один модуль высокого уровня, который является классом Calculator. Модули низкого уровня, с другой стороны, помогают модулям высокого уровня выполнять свою работу, обычно мы называем эти модули зависимостями. В нашем примере низкоуровневыми модулями являются классы Сложения, вычитания, умножения и деления.

Зависимости и связь

Как упоминалось в предыдущем параграфе, зависимости устанавливаются, когда модуль использует другой модуль для завершения своей работы. Например, модуль Calculator нуждается в модуле Add для достижения своей цели, поэтому устанавливается зависимость.

Связь – это степень взаимозависимости между двумя модулями, это показатель того, насколько тесно они связаны. Когда эта связь прочна и мы не можем изменить одну часть зависимости, не затронув другую часть, мы говорим, что существует тесная связь. С другой стороны, если мы можем изменить одну часть зависимости, не затрагивая другую часть, мы говорим, что существует слабая связь.

Мы всегда должны избегать тесной связи, потому что это нарушает принцип Open/Closed, не позволяя изменять одну сторону зависимости, не затрагивая другую.

Применение Принципа Инверсии Зависимостей

В конструкции системы калькулятора есть один фундаментальный недостаток: существует тесная связь между калькулятором и остальными низкоуровневыми модулями. Мы не можем изменить ни один из операционных модулей без изменения модуля калькулятора. Кроме того, если мы хотим добавить другую операцию, например, квадратный корень, мы также должны изменить класс Calculator, который нарушает принцип Open/Closed. Итак, как нам исправить эту проблему с дизайном? Легко, применяя принцип Инверсии зависимостей.

Первый сегмент нашего принципа гласит: “Модули высокого уровня не должны зависеть от модулей низкого уровня. И то, и другое должно зависеть от абстракций”. В нашем текущем проекте модуль калькулятора зависит от модуля Сложения, вычитания, деления и умножения. Чтобы соответствовать DI, мы должны определить абстракцию с именем “CalculatorOperation”. Как высокоуровневые, так и низкоуровневые модули будут зависеть от этой абстракции.

Второй сегмент принципа DIP гласит: “Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций”. Чтобы выполнить это правило, мы должны определить абстракцию как интерфейс (абстракцию), а не как класс (деталь).

Теперь существует слабая связь между модулем калькулятора и операционными модулями. Теперь вы можете изменить одну сторону зависимости, не затрагивая другую сторону. Вы также можете добавить дополнительные операции, если они реализуют абстракцию операций, не затрагивая модуль калькулятора.

Пример кодирования

Плохой Дизайн

Каждая операция калькулятора представлена в виде низкоуровневого модуля:

public class AddOperation {

    /**
     * Adds two numbers.
     * @param numA          First number.
     * @param numB          Second number.
     * @return              Result.
     */
    public double add(double numA, double numB){
        return numA + numB;
    }
}

public class SubtractOperation {

    /**
     * Subtracts two numbers.
     * @param numA          First number.
     * @param numB          Second number.
     * @return              Result.
     */
    public double subtract(double numA, double numB){
        return numA - numB;
    }
}

public class MultiplyOperation {

    /**
     * Multiplies two numbers.
     * @param numA          First number.
     * @param numB          Second number.
     * @return              Result.
     */
    public double multiply(double numA, double numB){
        return numA * numB;
    }
}

public class DivideOperation {

    /**
     * Divides two numbers.
     * @param numA          First number.
     * @param numB          Second number.
     * @return              Result.
     */
    public double divide(double numA, double numB){
        return numA / numB;
    }

}

Нарушение принципа инверсии зависимостей заметно в классе Calculator. Если мы хотим добавить новую операцию калькулятора, мы должны изменить класс Calculator, что нарушает принцип Open/Closed.

public class Calculator {

    public enum Operation{
        ADD, SUBTRACT, MULTIPLY, DIVIDE
    }


    /**
     * Performs a two numbers operation.
     * @param numA              First number.
     * @param numB              Second number.
     * @param operation         Type of operation.
     * @return                  Operation's result.
     */
    public double calculate(double numA, double numB, Operation operation){

        double result = 0;

        switch(operation){

            case ADD:
                AddOperation addOp = new AddOperation();
                result = addOp.add(numA, numB);
                break;
            case SUBTRACT:
                SubtractOperation subOp = new SubtractOperation();
                result = subOp.subtract(numA, numB);
                break;
            case MULTIPLY:
                MultiplyOperation multOp = new MultiplyOperation();
                result = multOp.multiply(numA, numB);
                break;
            case DIVIDE:
                DivideOperation divOp = new DivideOperation();
                result = divOp.divide(numA, numB);
                break;

        }

        return result;

    }
}

Чтобы решить эту проблему и соответствовать DIP и OCP, мы должны добавить абстракцию и изменить зависимости, чтобы как высокоуровневые, так и низкоуровневые модули зависели от абстракции.

Хороший Дизайн

public interface CalculatorOperation {

    public double calculate(double numbA, double numB);

}

public class AddOperation implements CalculatorOperation {

    @Override
    public double calculate(double numbA, double numB) {
        return numbA + numB;
    }   
}

public class SubtractOperation implements CalculatorOperation {

    @Override
    public double calculate(double numbA, double numB) {
        return numbA - numB;
    }
}

public class MultiplyOperation implements CalculatorOperation {

    @Override
    public double calculate(double numbA, double numB) {
        return numbA * numB;
    }  
}

public class DivideOperation implements CalculatorOperation {

    @Override
    public double calculate(double numbA, double numB) {
        return numbA / numB;
    }
}

Теперь класс Calculator соответствует принципу Инверсии зависимостей.

public class Calculator {

    /**
     * Performs a two numbers operation.
     * @param numA              First number.
     * @param numB              Second number.
     * @param operation         Type of operation.
     * @return                  Operation's result.
     */
    public double calculate(double numA, double numB, CalculatorOperation operation){
        return operation.calculate(numB, numB);
    }
}

Заключительные мысли

Мы рассмотрели пять ОСНОВНЫХ принципов, и вы увидели преимущества и недостатки каждого из них. Помните, что считалось, что ТВЕРДЫЕ принципы помогут вам достичь гибкости, удобочитаемости и возможности повторного использования. Некоторые из этих принципов являются краеугольным камнем множества фреймворков и архитектур, и вы выиграете от их реализации. Но также помните, что чрезмерное или неправильное использование этих принципов приведет к чрезмерному усложнению вашего кода. Вы должны оценить каждый вариант использования и решить, какой из них лучше всего подходит для вашего решения.

Если вы хотите узнать больше о DIP, вы можете заглянуть в Блог Мартина Фаулера .

Оригинал: “https://dev.to/victorpinzon1988eng/solid-dependency-inversion-principle-5f8m”