Prinsip Dketergantungan Inversi P(dalam C++) adalah prinsip desain kelima & terakhir dari seri SOLID sebagai prinsip desain Batu. Prinsip desain SOLID fokus pada pengembangan perangkat lunak yang mudah dipelihara, dapat digunakan kembali, dan dapat diperluas. Pada artikel ini, kita akan melihat contoh kode beserta alurnya & memperbaikinya dengan bantuan DIP. Kita juga akan melihat pedoman & manfaat DIP di penutup artikel.

/!\: Awalnya diterbitkan @ www.vishalchovatiya.com.

Omong-omong, Jika Anda belum membaca artikel saya sebelumnya tentang prinsip desain, di bawah ini adalah tautan cepatnya:

  1. SRP — Prinsip Tanggung Jawab Tunggal
  2. OCP — Prinsip Terbuka/Tertutup
  3. LSP — Prinsip Substitusi Liskov
  4. ISP — Prinsip Pemisahan Antarmuka
  5. DIP — Prinsip Pembalikan Ketergantungan

Cuplikan kode yang Anda lihat di seluruh rangkaian artikel ini disederhanakan, bukan canggih. Jadi Anda sering melihat saya tidak menggunakan kata kunci seperti override, final, public(sementara warisan) hanya untuk membuat kode menjadi ringkas & dapat dikonsumsi(sebagian besar waktu) dalam satu ukuran layar standar. Saya juga lebih suka struct daripada class hanya untuk menyimpan baris dengan tidak menulis 'public:' kadang-kadang dan juga melewatkan destruktor virtual, konstruktor, salin konstruktor, awalan std::, sengaja menghapus memori dinamis. Saya juga menganggap diri saya orang pragmatis yang ingin menyampaikan ide dengan cara sesederhana mungkin dibandingkan dengan cara baku atau menggunakan jargon.

Catatan:

  • Jika Anda langsung menemukan di sini, saya sarankan Anda membaca Apa itu pola desain? pertama, meskipun itu sepele. Saya yakin ini akan mendorong Anda untuk mengeksplorasi topik ini lebih jauh.
  • Semua kode yang Anda temui dalam rangkaian artikel ini dikompilasi menggunakan C++20 (walaupun saya telah menggunakan fitur Modern C++ hingga C++17 dalam banyak kasus). Jadi jika Anda tidak memiliki akses ke kompiler terbaru, Anda dapat menggunakan https://wandbox.org/ yang juga telah menginstal pustaka boost.

Maksud

=› Modul tingkat tinggi tidak boleh bergantung pada modul tingkat rendah. Keduanya harus bergantung pada abstraksi.
=› Abstraksi tidak boleh bergantung pada detail. Detailnya harus bergantung pada abstraksi.

  • Baris di atas mungkin tampak samar pada awalnya, tetapi jangan teruskan di sini. Anda akan mendapatkannya dengan memberi contoh.

Apa yang dimaksud dengan modul Tingkat Tinggi & Tingkat Rendah?

=› Modul tingkat tinggi: menjelaskan operasi yang lebih bersifat abstrak & berisi logika yang lebih kompleks. Modul-modul ini mengatur modul tingkat rendah dalam aplikasi kita.
=› Modul tingkat rendah: menjelaskan implementasi lebih spesifik & individual untuk komponen yang berfokus pada detail & bagian aplikasi yang lebih kecil. Modul-modul ini digunakan di dalam modul tingkat tinggi.

Melanggar Prinsip Pembalikan Ketergantungan

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;
}
  • Ketika nanti wadah Relationships berubah dari vector menjadi set atau wadah lainnya, Anda perlu mengubah di banyak tempat yang desainnya tidak terlalu bagus. Sekalipun hanya nama anggota data yaitu Relationships::m_relations yang berubah, Anda akan mendapati diri Anda melanggar bagian kode lainnya.
  • Seperti yang Anda lihat Modul tingkat rendah yaitu Relationships secara langsung bergantung pada modul Tingkat Tinggi yaitu Research yang pada dasarnya merupakan pelanggaran DIP.

Contoh Prinsip Pembalikan Ketergantungan

  • Sebaliknya kita harus membuat abstraksi dan mengikat modul Tingkat Rendah & Tingkat Tinggi ke abstraksi itu. Pertimbangkan perbaikan berikut:
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;
    //      }
    //    }
    //  }
};
  • Sekarang tidak masalah, nama container atau perubahan container itu sendiri di Modul tingkat rendah, Modul tingkat tinggi, atau bagian kode lain yang mengikuti DIP akan tetap utuh.
  • Prinsip Dependency Inversion (DIP) menyatakan bahwa sistem yang paling fleksibel adalah sistem yang ketergantungan kode sumbernya hanya mengacu pada abstraksi, bukan konkresi.
  • Inilah alasan mengapa sebagian besar pengembang berpengalaman menggunakan fungsi STL atau perpustakaan bersama dengan wadah umum. Bahkan menggunakan kata kunci auto di tempat yang tepat dapat membantu menciptakan perilaku umum dengan kode yang tidak terlalu rapuh.
  • Ada banyak cara untuk mengimplementasikan DIP, selama menyangkut C++, kebanyakan orang menggunakan polimorfisme statis (yaitu CRTP kecuali mereka memerlukan yang dinamis), spesialisasi template, Pola Desain Adaptor, penghapusan tipe, dll.

Tolok ukur untuk Perangkat Lunak Ramah Prinsip Pembalikan Ketergantungan Kerajinan (DIP).

  • Jika Anda merasa sulit menerapkan DIP, maka rancang saja abstraksi terlebih dahulu & implementasikan modul tingkat tinggi Anda berdasarkan abstraksi. Tanpa memiliki pengetahuan tentang modul tingkat rendah atau implementasinya. Karena proses ini DIP juga dikenal sebagai Coding To Interface.
  • Perlu diingat bahwa semua modul tingkat rendah/"subkelas" mematuhi "Prinsip Substitusi Liskov". Hal ini karena modul tingkat rendah/"subkelas" akan digunakan melalui antarmuka abstrak, bukan antarmuka kelas konkrit.

Manfaat

=› Dapat digunakan kembali

  • Secara efektif, DIP mengurangi penggabungan antar bagian kode yang berbeda. Jadi kita mendapatkan kode yang dapat digunakan kembali.

=› Pemeliharaan

  • Penting juga untuk menyebutkan bahwa mengubah modul yang sudah diterapkan adalah hal yang berisiko. Dengan bergantung pada abstraksi & bukan pada implementasi konkrit, kita dapat mengurangi risiko tersebut dengan tidak perlu mengubah modul tingkat tinggi dalam proyek kita.
  • Terakhir, DIP bila diterapkan dengan benar memberi kita fleksibilitas dan stabilitas di tingkat keseluruhan arsitektur aplikasi kita. Aplikasi kita akan dapat berkembang dengan lebih aman dan menjadi stabil & tangguh.

Kesimpulan

Seperti yang Anda lihat, kami mengambil contoh dasar kode & mengubahnya menjadi kode yang dapat digunakan kembali, fleksibel & modular. Jika saya harus merangkum DIP dalam kalimat sederhana & pendek maka akan menjadi seperti:

Jangan menggunakan benda beton secara langsung kecuali Anda mempunyai alasan kuat untuk melakukannya. Gunakan abstraksi sebagai gantinya.

DIP melatih kita untuk berpikir tentang kelas dalam kaitannya dengan perilaku, bukan konstruksi atau implementasi.

Punya Saran, Pertanyaan, atau Ingin Dikatakan Hi? Hilangkan Tekanan, Anda Hanya Dengan Sekali Klik.🖱️