Принципы SOLID Design: принцип замещения Лискова и принцип инверсии зависимостей

Просто подумайте и задайте вопрос сообществу разработчиков stackru и Microsoft о принципах объектно-ориентированного проектирования программного обеспечения под названием SOLID. В чем разница между принципом подстановки Лискова и принципом инверсии зависимостей, пожалуйста? Я думал об этом некоторое время и не уверен в разнице. Пожалуйста, дайте мне знать? Любые мысли / отзывы очень приветствуются. Большое спасибо и наилучшие пожелания, Тахер Хассан

3 ответа

Принцип подстановки Лискова гласит следующее:

Класс должен быть напрямую заменен своим базовым классом.

Это означает, что если дочерний класс расширяет родительский класс, он должен быть напрямую заменяемым.Изображение можно просмотреть здесь

Если вы посмотрите, возьмем для примера Птицу. Не все птицы летают, но некоторые летают.

Давайте посмотрим на пример java:

Bird  ostrich = new Ostrich();

Если я собираюсь относиться к своему страусу как к птице (это базовый / родительский класс), и у нас есть функциональность в Bird, чтобы этот страус мог летать, даже если они не должны!

Таким образом, мы можем выделить структуру:Отредактированная иерархия для Лискова

Инверсия зависимостей проще, если рассматривать ее как отдельный принцип.

Если у нас есть класс, вызовите класс. Из-за этого очень сложно изменить его позже, и нам нужно будет изменить исходный код. Снова давайте посмотрим на пример кода.

public class App {
    public static void main(String[] args) {
        Greeting greeting = new Greeting();
        greeting.sayHello(new Friend()); 
        greeting.sayHello(new Enemy());
    }
}

public class Greeting {
    public void sayHello(Person person) {
         person.greet();
    }
}

public interface Person {
    public void greet();
}

public class Friend implements Person {
    public void greet() {
        System.out.println("Hello my friend");
    }
}

public class Enemy implements Person {
    public void greet() {
        System.out.println("Go away");
    }
}

Здесь мы передаем родительский объект (Person) обоих элементов (Friend и Enemy). Если бы мы прошли через объект Friend, нам понадобился бы отдельный идентичный метод для метода Enemy. Мы можем использовать принцип Open/Closed, чтобы иметь единственный метод, который может вызывать либо Friend, либо Enemy, или что-то еще в будущем, что может расширить Person. Инверсия зависимостей заключается в том, что вместо метода sayHello(), создающего класс, передается родительский объект. Это означает, что вызываемый нами родительский объект зависит от приложения, а не от sayHello(), определяющего, какой объект вызывать.

Лучше использовать инверсию зависимостей. Вызов класса greet() не высечен в камне, если переданный класс реализует Person.

Это означает, что приложение не зависит от друга. Будет ли вызван Друг или Враг, зависит от приложения.

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

В каком-то смысле LSP и DIP - противоположности.

  • LSP управляет отношениями между классами в иерархии наследования
    (т.е. классами, которые являются родительскими и дочерними).
  • DIP управляет отношениями между классами вне иерархии наследования
    (то есть классами, которые не являются родительскими и дочерними).

Для наглядности рассмотрим шесть потенциальных отношений между двумя родительскими и двумя дочерними классами.

  • LSP занимается отношениями 1 и 2.
    • 1 и 2 подходят, только если Child1 и Child2 являются подтипами, а не только подклассами.
  • DIP касается отношений 3, 4, 5 и 6.
    • 3 в порядке.
    • 6 запрещено.
    • 4 и 5 могут указывать вверх, но не вниз.

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

Внедрение зависимостей делает ваш код лучше, поскольку сокращает ненужные связи между классами. Это, опять же, делает эти классы повторно используемыми в разных сценариях. Например, если ваш класс получает данные из файла, вы столкнетесь с проблемами в следующий раз, когда ваши данные будут в базе данных. Ваш класс внезапно превратится в болото случаев «если-то-еще», которые пытаются иметь дело с разными вещами, и поддерживать его будет кошмаром. Внедрение зависимостей освобождает ваш класс от этой ответственности - то есть вашему классу просто «нужны данные», и вызывающая сторона несет ответственность за то, чтобы убедиться, что данные подключены (или «введены») в него. Технически внедрение зависимости - это просто установка переменной, например , или же .

Другие вопросы по тегам