Принцип D версии P (в C ++) является пятым и последним принципом проектирования в серии принципов проектирования SOLID as a Rock. Принципы проектирования SOLID сосредоточены на разработке программного обеспечения, которое легко поддерживать, использовать повторно и расширять. В этой статье мы увидим пример кода с потоком и исправим его с помощью DIP. Мы также увидим руководство и преимущества DIP в конце статьи.
/! \: Первоначально опубликовано на @ www.vishalchovatiya.com.
Кстати, если вы не читали мои предыдущие статьи о принципах дизайна, то ниже вы найдете быстрые ссылки:
- S RP - Принцип единой ответственности
- O CP - Принцип открытости / закрытости
- L SP - Принцип замены Лискова
- I SP - Принцип разделения интерфейса
- 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
? Избавьтесь от давления, вы на расстоянии одного клика . 🖱️