Принцип D версии P (в C ++) является пятым и последним принципом проектирования в серии принципов проектирования SOLID as a Rock. Принципы проектирования SOLID сосредоточены на разработке программного обеспечения, которое легко поддерживать, использовать повторно и расширять. В этой статье мы увидим пример кода с потоком и исправим его с помощью DIP. Мы также увидим руководство и преимущества DIP в конце статьи.

/! \: Первоначально опубликовано на @ www.vishalchovatiya.com.

Кстати, если вы не читали мои предыдущие статьи о принципах дизайна, то ниже вы найдете быстрые ссылки:

  1. S RP - Принцип единой ответственности
  2. O CP - Принцип открытости / закрытости
  3. L SP - Принцип замены Лискова
  4. I SP - Принцип разделения интерфейса
  5. D IP - Принцип инверсии зависимостей

Фрагменты кода, которые вы видите в этой серии статей, упрощены, а не сложны. Поэтому вы часто видите, что я не использую такие ключевые слова, как override, final, public (при наследовании), просто чтобы сделать код компактным и потребляемым (большую часть времени) на одном стандартном размере экрана. Я также предпочитаю struct вместо class, просто чтобы сохранить строку, не записывая иногда public:, а также пропускаю виртуальный деструктор, конструктор, конструктор копирования, префикс std::, намеренно удаляя динамическую память. Я также считаю себя прагматичным человеком, который хочет передать идею максимально простым способом, а не стандартным способом или с использованием жаргонов.

Примечание.

  • Если вы случайно наткнулись здесь, то я бы посоветовал вам пройти через Что такое шаблон проектирования? во-первых, даже если это банально. Я считаю, что это побудит вас больше исследовать эту тему.
  • Весь этот код, с которым вы сталкиваетесь в этой серии статей, скомпилирован с использованием C ++ 20 (хотя в большинстве случаев я использовал функции Modern C ++ вплоть до C ++ 17). Поэтому, если у вас нет доступа к последней версии компилятора, вы можете использовать https://wandbox.org/, в котором также есть предустановленная библиотека ускорения.

Намерение

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

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

Что такое модули высокого и низкого уровня?

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

Нарушение принципа инверсии зависимостей

enum class Relationship { parent, child, sibling };
struct Person {
    string      m_name;
};
struct Relationships {      // Low-level <<<<<<<<<<<<-------------------------
    vector<tuple<Person, Relationship, Person>>     m_relations;
    void add_parent_and_child(const Person &parent, const Person &child) {
        m_relations.push_back({parent, Relationship::parent, child});
        m_relations.push_back({child, Relationship::child, parent});
    }
};
struct Research {           // High-level  <<<<<<<<<<<<------------------------
    Research(const Relationships &relationships) {
        for (auto &&[first, rel, second] : relationships.m_relations) {// Need C++17 here
            if (first.m_name == "John" && rel == Relationship::parent)
                cout << "John has a child called " << second.m_name << endl;
        }
    }
};
int main() {
    Person parent{"John"};
    Person child1{"Chris"};
    Person child2{"Matt"};
    Relationships relationships;
    relationships.add_parent_and_child(parent, child1);
    relationships.add_parent_and_child(parent, child2);
    Research _(relationships);
    return EXIT_SUCCESS;
}
  • Когда позже контейнер Relationships изменится с vector на set или любой другой контейнер, вам нужно будет поменять во многих местах, что не очень хороший дизайн. Даже если изменится только имя элемента данных, т.е. Relationships::m_relations, вы обнаружите, что нарушаете другие части кода.
  • Как вы можете видеть, модуль низкого уровня, т.е. Relationships, напрямую зависит от модуля высокого уровня, то есть Research, что по сути является нарушением DIP.

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

  • Скорее, мы должны создать абстракцию и привязать к ней низкоуровневый и высокоуровневый модуль. Рассмотрим следующее исправление:
struct RelationshipBrowser {
    virtual vector<Person> find_all_children_of(const string &name) = 0;
};
struct Relationships : RelationshipBrowser {     // Low-level <<<<<<<<<<<<<<<------------------------
    vector<tuple<Person, Relationship, Person>>     m_relations;
    void add_parent_and_child(const Person &parent, const Person &child) {
        m_relations.push_back({parent, Relationship::parent, child});
        m_relations.push_back({child, Relationship::child, parent});
    }
    vector<Person> find_all_children_of(const string &name) {
        vector<Person> result;
        for (auto &&[first, rel, second] : m_relations) {
            if (first.name == name && rel == Relationship::parent) {
                result.push_back(second);
            }
        }
        return result;
    }
};
struct Research {                                // High-level <<<<<<<<<<<<<<<----------------------
    Research(RelationshipBrowser &browser) {
        for (auto &child : browser.find_all_children_of("John")) {
            cout << "John has a child called " << child.name << endl;
        }
    }
    //  Research(const Relationships& relationships)
    //  {
    //    auto& relations = relationships.relations;
    //    for (auto&& [first, rel, second] : relations)
    //    {
    //      if (first.name == "John" && rel == Relationship::parent)
    //      {
    //        cout << "John has a child called " << second.name << endl;
    //      }
    //    }
    //  }
};
  • Теперь независимо от того, изменится ли имя контейнера или сам контейнер в низкоуровневом модуле, высокоуровневом модуле или других частях кода, которые следовали за DIP, будут нетронутыми.
  • Принцип инверсии зависимостей (DIP) предполагает, что наиболее гибкими являются системы, в которых зависимости исходного кода относятся только к абстракциям, а не к конкрециям.
  • Это причина, по которой наиболее опытные разработчики используют STL или библиотечные функции вместе с универсальными контейнерами. Даже использование ключевого слова auto в соответствующих местах может помочь в создании общего поведения с менее хрупким кодом.
  • Есть много способов реализовать DIP, если в C ++ большинство людей используют статический полиморфизм (например, CRTP, если им не нужен динамический), специализацию шаблона, шаблон проектирования адаптера, стирание типов и т. Д.

Yardstick для создания программного обеспечения, совместимого с принципом инверсии зависимостей (DIP)

  • Если вам сложно обеспечить соблюдение DIP, просто сначала спроектируйте абстракцию и реализуйте свой высокоуровневый модуль на основе абстракции. Без каких-либо знаний о модуле низкого уровня или его реализации. Из-за этого процесса DIP также известен как Coding To Interface.
  • Имейте в виду, что все низкоуровневые модули / подклассы придерживаются Принципа замены Лискова. Это потому, что низкоуровневые модули / подклассы будут использоваться через абстрактный интерфейс, а не через интерфейс конкретных классов.

Преимущества

= ›Возможность повторного использования

  • По сути, DIP снижает взаимосвязь между различными частями кода. Таким образом мы получаем многоразовый код.

= ›Ремонтопригодность

  • Также важно отметить, что изменение уже реализованных модулей рискованно. В зависимости от абстракции, а не от конкретной реализации, мы можем снизить этот риск, не изменяя высокоуровневые модули в нашем проекте.
  • Наконец, при правильном применении DIP дает нам гибкость и стабильность на уровне всей архитектуры нашего приложения. Наше приложение сможет развиваться более безопасно, станет стабильным и надежным.

Заключение

Как видите, мы взяли базовый пример кода и преобразовали его в многоразовый, гибкий и модульный фрагмент кода. Если бы мне пришлось резюмировать DIP в простом и коротком предложении, это было бы примерно так:

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

DIP учит нас думать о классах с точки зрения поведения, а не построения или реализации.

Есть предложения, вопросы или пожелания Hi? Избавьтесь от давления, вы на расстоянии одного клика . 🖱️