Apa prinsip inversi ketergantungan dan mengapa itu penting?

178

Apa prinsip inversi ketergantungan dan mengapa itu penting?

Phillip Wells
sumber
3
Kuantitas jawaban yang konyol di sini menggunakan istilah "tingkat tinggi" dan "tingkat rendah" dari Wikipedia. Istilah-istilah ini tidak dapat diakses dan mengarahkan banyak pembaca ke halaman ini. Jika Anda ingin memuntahkan wikipedia, harap tentukan istilah-istilah ini dalam jawaban Anda untuk memberikan konteks!
8bitjunkie

Jawaban:

107

Lihat dokumen ini: Prinsip Pembalikan Ketergantungan .

Pada dasarnya dikatakan:

  • Modul tingkat tinggi tidak boleh bergantung pada modul tingkat rendah. Keduanya harus bergantung pada abstraksi.
  • Abstraksi tidak boleh bergantung pada detail. Detail harus bergantung pada abstraksi.

Singkatnya, mengapa itu penting, perubahan itu berisiko, dan dengan bergantung pada konsep alih-alih pada implementasi, Anda mengurangi kebutuhan akan perubahan di situs panggilan.

Secara efektif, DIP mengurangi kopling antara potongan kode yang berbeda. Idenya adalah bahwa meskipun ada banyak cara untuk mengimplementasikan, katakanlah, fasilitas logging, cara Anda akan menggunakannya harus relatif stabil dalam waktu. Jika Anda dapat mengekstrak antarmuka yang mewakili konsep logging, antarmuka ini akan jauh lebih stabil dalam waktu daripada implementasinya, dan situs panggilan harus jauh lebih sedikit dipengaruhi oleh perubahan yang Anda buat sambil mempertahankan atau memperluas mekanisme logging tersebut.

Dengan juga membuat implementasi bergantung pada sebuah antarmuka, Anda mendapatkan kemungkinan untuk memilih pada saat run-time implementasi mana yang lebih cocok untuk lingkungan khusus Anda. Tergantung pada kasusnya, ini mungkin menarik juga.

Carl Seleborg
sumber
31
Jawaban ini tidak mengatakan mengapa DIP itu penting, atau bahkan apa DIP itu. Saya telah membaca dokumen DIP resmi, dan berpikir ini benar-benar "prinsip" yang buruk dan tidak dapat dibenarkan, karena didasarkan pada asumsi yang salah: bahwa modul tingkat tinggi dapat digunakan kembali.
Rogério
3
Pertimbangkan grafik ketergantungan untuk beberapa objek. Terapkan DIP ke objek. Sekarang objek apa pun akan bergantung pada implementasi objek lainnya. Pengujian unit sekarang sederhana. Kemudian refactoring untuk digunakan kembali adalah mungkin. Perubahan desain memiliki cakupan perubahan yang sangat terbatas. Masalah desain jangan sampai kaskade. Lihat juga pola AI "Papan Tulis" untuk inversi deposisi data. Bersama-sama, alat yang sangat kuat untuk membuat perangkat lunak dapat dimengerti, dipelihara dan dapat diandalkan. Abaikan injeksi ketergantungan dalam konteks ini. Itu tidak berhubungan.
Tim Williscroft
6
Kelas A yang menggunakan kelas B tidak berarti A "tergantung" pada implementasi B; itu tergantung pada antarmuka publik B saja. Menambahkan abstraksi terpisah di mana A dan B sekarang hanya bergantung berarti bahwa A tidak akan lagi memiliki ketergantungan waktu kompilasi pada antarmuka publik B. Isolasi antar unit dapat dicapai dengan mudah tanpa abstraksi ekstra ini; ada alat mengejek khusus untuk itu, di Jawa dan .NET, yang menangani semua situasi (metode statis, konstruktor, dll.). Menerapkan DIP cenderung membuat perangkat lunak lebih kompleks dan kurang terawat, dan tidak lebih dapat diuji.
Rogério
1
Lalu apa inversi kontrol? cara untuk mencapai Pembalikan Ketergantungan?
user20358
2
@Rogerio, lihat respons Derek Greer di bawah, ia menjelaskannya. AFAIK, DIP mengatakan bahwa A yang mengamanatkan apa yang dibutuhkan A, dan bukan B yang mengatakan apa yang dibutuhkan A. Jadi, antarmuka yang dibutuhkan A tidak boleh diberikan oleh B, tetapi untuk A.
ejaenv
145

Buku Pengembangan Perangkat Lunak Agile, Prinsip, Pola, dan Praktik serta Prinsip, Pola, dan Praktek Agile dalam C # adalah sumber daya terbaik untuk memahami sepenuhnya tujuan dan motivasi asli di balik Prinsip Ketergantungan Inversi. Artikel "Prinsip Ketergantungan Pembalikan" juga merupakan sumber yang bagus, tetapi karena fakta bahwa itu adalah versi ringkas dari draft yang akhirnya masuk ke dalam buku-buku yang disebutkan sebelumnya, itu meninggalkan beberapa diskusi penting tentang konsep suatu kepemilikan paket dan antarmuka yang merupakan kunci untuk membedakan prinsip ini dari saran yang lebih umum untuk "program ke antarmuka, bukan implementasi" ditemukan dalam Pola Desain buku (Gamma, et. al).

Untuk memberikan ringkasan, Prinsip Pembalikan Ketergantungan terutama tentang membalikkan arah ketergantungan konvensional dari komponen "tingkat lebih tinggi" ke komponen "tingkat lebih rendah" sehingga komponen "tingkat bawah" bergantung pada antarmuka yang dimiliki oleh komponen "tingkat lebih tinggi" . (Catatan: komponen "tingkat yang lebih tinggi" di sini mengacu pada komponen yang membutuhkan dependensi / layanan eksternal, belum tentu posisi konseptualnya dalam arsitektur berlapis). Dalam melakukan hal itu, kopling tidak berkurang sebanyak ia bergeser dari komponen yang secara teoritis kurang berharga untuk komponen yang secara teori lebih berharga.

Hal ini dicapai dengan mendesain komponen yang dependensi eksternalnya dinyatakan dalam bentuk antarmuka yang implementasinya harus disediakan oleh konsumen komponen tersebut. Dengan kata lain, antarmuka yang didefinisikan mengekspresikan apa yang dibutuhkan oleh komponen, bukan bagaimana Anda menggunakan komponen (misalnya "INeedSomething", bukan "IDoSomething").

Apa yang tidak dirujuk oleh Prinsip Inversi Ketergantungan adalah praktik sederhana mengabstraksi dependensi melalui penggunaan antarmuka (mis. MyService → [ILogger ⇐ Logger]). Walaupun ini memisahkan komponen dari detail implementasi spesifik dari dependensi, itu tidak membalikkan hubungan antara konsumen dan ketergantungan (mis. [MyService → IMyServiceLogger] ⇐ Logger.

Pentingnya Prinsip Ketergantungan Inversi dapat didistilasi ke tujuan tunggal untuk dapat menggunakan kembali komponen perangkat lunak yang bergantung pada dependensi eksternal untuk sebagian dari fungsinya (logging, validasi, dll.)

Dalam tujuan umum penggunaan kembali ini, kami dapat menggambarkan dua sub-jenis penggunaan kembali:

  1. Menggunakan komponen perangkat lunak dalam banyak aplikasi dengan implementasi sub-dependensi (mis. Anda telah mengembangkan wadah DI dan ingin memberikan pencatatan, tetapi tidak ingin memasangkan wadah Anda ke logger tertentu sehingga setiap orang yang menggunakan wadah Anda juga harus gunakan perpustakaan logging yang Anda pilih).

  2. Menggunakan komponen perangkat lunak dalam konteks yang berkembang (misalnya Anda telah mengembangkan komponen logika bisnis yang tetap sama di beberapa versi aplikasi di mana detail implementasi berkembang).

Dengan kasus pertama menggunakan kembali komponen di beberapa aplikasi, seperti dengan perpustakaan infrastruktur, tujuannya adalah untuk menyediakan kebutuhan infrastruktur inti kepada konsumen Anda tanpa menyatukan konsumen Anda dengan sub-dependensi perpustakaan Anda sendiri karena mengambil dependensi pada dependensi tersebut mengharuskan Anda konsumen membutuhkan ketergantungan yang sama juga. Ini bisa menjadi masalah ketika konsumen perpustakaan Anda memilih untuk menggunakan perpustakaan yang berbeda untuk kebutuhan infrastruktur yang sama (misalnya NLog vs log4net), atau jika mereka memilih untuk menggunakan versi terbaru dari perpustakaan yang diperlukan yang tidak kompatibel dengan versi. diperlukan oleh perpustakaan Anda.

Dengan kasus kedua menggunakan kembali komponen logika bisnis (yaitu "komponen tingkat lebih tinggi"), tujuannya adalah untuk mengisolasi implementasi domain inti dari aplikasi Anda dari perubahan kebutuhan detail implementasi Anda (mis. Mengubah / meningkatkan perpustakaan persistensi, perpustakaan perpesanan) , strategi enkripsi, dll.). Idealnya, mengubah rincian implementasi aplikasi tidak boleh merusak komponen yang merangkum logika bisnis aplikasi.

Catatan: Beberapa orang mungkin keberatan untuk menggambarkan kasus kedua ini sebagai penggunaan kembali yang sebenarnya, dengan alasan bahwa komponen seperti komponen logika bisnis yang digunakan dalam aplikasi yang berkembang hanya mewakili penggunaan tunggal. Idenya di sini, bagaimanapun, adalah bahwa setiap perubahan pada detail implementasi aplikasi membuat konteks baru dan oleh karena itu use case yang berbeda, meskipun tujuan akhir dapat dibedakan sebagai isolasi vs portabilitas.

Meskipun mengikuti Prinsip Ketergantungan Inversi dalam kasus kedua ini dapat menawarkan beberapa manfaat, perlu dicatat bahwa nilainya seperti yang diterapkan pada bahasa modern seperti Java dan C # jauh berkurang, mungkin sampai pada titik yang tidak relevan. Seperti dibahas sebelumnya, DIP melibatkan pemisahan rincian implementasi ke dalam paket yang terpisah sepenuhnya. Akan tetapi, dalam kasus aplikasi yang berkembang, hanya dengan menggunakan antarmuka yang didefinisikan dalam domain bisnis akan mencegah tidak perlu memodifikasi komponen tingkat yang lebih tinggi karena perubahan kebutuhan komponen detail implementasi, bahkan jika detail implementasi akhirnya berada dalam paket yang sama . Bagian dari prinsip ini mencerminkan aspek-aspek yang berkaitan dengan bahasa yang dilihat ketika prinsip tersebut dikodifikasi (yaitu C ++) yang tidak relevan dengan bahasa yang lebih baru. Yang mengatakan,

Diskusi yang lebih panjang tentang prinsip ini terkait dengan penggunaan antarmuka yang sederhana, Dependency Injection, dan pola Interface Terpisah dapat ditemukan di sini . Selain itu, diskusi tentang bagaimana prinsip ini terkait dengan bahasa yang diketik secara dinamis seperti JavaScript dapat ditemukan di sini .

Derek Greer
sumber
17
Terima kasih. Sekarang saya mengerti bagaimana jawaban saya tidak tepat. Perbedaan antara MyService → [ILogger ⇐ Logger] dan [MyService → IMyServiceLogger] ⇐ Logger halus tetapi penting.
Patrick McElhaney
2
Dalam baris yang sama dijelaskan dengan sangat baik di sini: lostechies.com/derickbailey/2011/09/22/…
ejaenv
1
Betapa saya berharap orang akan berhenti menggunakan penebang sebagai usecase kanonik untuk injeksi ketergantungan. Terutama sehubungan dengan log4net di mana hampir merupakan pola anti. Selain itu, penjelasan kickass!
Casper Leon Nielsen
4
@ Casper Leon Nielsen - DIP tidak ada hubungannya dengan DI. Mereka bukan sinonim atau konsep yang setara.
TSmith
4
@ VF1 Sebagaimana dinyatakan dalam paragraf ringkasan, pentingnya Prinsip Ketergantungan Inversi terutama dengan penggunaan kembali. Jika John merilis perpustakaan yang memiliki ketergantungan eksternal pada perpustakaan logging dan Sam ingin menggunakan perpustakaan John, Sam mengambil ketergantungan logging sementara. Sam tidak akan pernah bisa menerapkan aplikasinya tanpa perpustakaan logging yang dipilih John. Namun, jika John mengikuti DIP, Sam bebas untuk memberikan adaptor dan menggunakan perpustakaan logging apa pun yang ia pilih. DIP bukan tentang kenyamanan, tetapi kopling.
Derek Greer
14

Ketika kita merancang aplikasi perangkat lunak, kita dapat mempertimbangkan kelas tingkat rendah kelas yang mengimplementasikan operasi dasar dan primer (akses disk, protokol jaringan, ...) dan kelas tingkat tinggi kelas yang merangkum logika kompleks (arus bisnis, ...).

Yang terakhir bergantung pada kelas tingkat rendah. Cara alami untuk menerapkan struktur seperti itu adalah dengan menulis kelas tingkat rendah dan begitu kita memilikinya untuk menulis kelas tingkat tinggi yang kompleks. Karena kelas tingkat tinggi didefinisikan dalam hal orang lain, ini tampaknya cara logis untuk melakukannya. Tapi ini bukan desain yang fleksibel. Apa yang terjadi jika kita perlu mengganti kelas tingkat rendah?

Prinsip Ketergantungan Pembalikan menyatakan bahwa:

  • Modul tingkat tinggi seharusnya tidak tergantung pada modul tingkat rendah. Keduanya harus bergantung pada abstraksi.
  • Abstraksi tidak harus bergantung pada detail. Detail harus bergantung pada abstraksi.

Prinsip ini berupaya untuk "membalikkan" gagasan konvensional bahwa modul tingkat tinggi dalam perangkat lunak harus bergantung pada modul tingkat bawah. Di sini modul tingkat tinggi memiliki abstraksi (misalnya, memutuskan metode antarmuka) yang diimplementasikan oleh modul tingkat bawah. Dengan demikian membuat modul level bawah tergantung pada modul level yang lebih tinggi.

nikhil.singhal
sumber
11

Ketergantungan inversi diterapkan dengan baik memberikan fleksibilitas dan stabilitas di tingkat seluruh arsitektur aplikasi Anda. Ini akan memungkinkan aplikasi Anda berkembang lebih aman dan stabil.

Arsitektur berlapis tradisional

Secara tradisional UI arsitektur berlapis tergantung pada lapisan bisnis dan ini pada gilirannya tergantung pada lapisan akses data.

Anda harus memahami layer, paket, atau pustaka. Mari kita lihat bagaimana kodenya.

Kami akan memiliki pustaka atau paket untuk lapisan akses data.

// DataAccessLayer.dll
public class ProductDAO {

}

Dan perpustakaan lain atau logika paket bisnis lapisan yang bergantung pada lapisan akses data.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

Arsitektur berlapis dengan inversi ketergantungan

Pembalikan ketergantungan menunjukkan hal berikut:

Modul tingkat tinggi seharusnya tidak bergantung pada modul tingkat rendah. Keduanya harus bergantung pada abstraksi.

Abstraksi tidak harus bergantung pada detail. Detail harus bergantung pada abstraksi.

Apa saja modul tingkat tinggi dan tingkat rendah? Modul-modul berpikir seperti perpustakaan atau paket, modul tingkat tinggi adalah modul-modul yang secara tradisional memiliki dependensi dan level rendah di mana mereka bergantung.

Dengan kata lain, modul level tinggi akan menjadi tempat tindakan dipanggil dan tingkat rendah di mana tindakan dilakukan.

Kesimpulan yang masuk akal untuk ditarik dari prinsip ini adalah bahwa seharusnya tidak ada ketergantungan antar konkret, tetapi harus ada ketergantungan pada abstraksi. Tetapi menurut pendekatan yang kami ambil, kami dapat menyalahgunakan ketergantungan investasi, tetapi abstraksi.

Bayangkan kita mengadaptasi kode kita sebagai berikut:

Kami akan memiliki pustaka atau paket untuk lapisan akses data yang mendefinisikan abstraksi.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

Dan perpustakaan lain atau logika paket bisnis lapisan yang bergantung pada lapisan akses data.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

Meskipun kami bergantung pada ketergantungan abstrak antara bisnis dan akses data tetap sama.

Untuk mendapatkan inversi dependensi, antarmuka persistensi harus didefinisikan dalam modul atau paket di mana logika atau domain tingkat tinggi ini dan tidak dalam modul tingkat rendah.

Pertama-tama tentukan apa lapisan domain itu dan abstraksi komunikasinya adalah kegigihan.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

Setelah lapisan kegigihan tergantung pada domain, dapatkan untuk membalikkan sekarang jika ketergantungan ditentukan.

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(sumber: xurxodev.com )

Memperdalam prinsip

Penting untuk mengasimilasi konsep dengan baik, memperdalam tujuan dan manfaat. Jika kita tinggal secara mekanis dan mempelajari repositori kasus tipikal, kita tidak akan dapat mengidentifikasi di mana kita dapat menerapkan prinsip ketergantungan.

Tetapi mengapa kita membalikkan ketergantungan? Apa tujuan utama di luar contoh spesifik?

Seperti umumnya memungkinkan hal-hal yang paling stabil, yang tidak tergantung pada hal-hal yang kurang stabil, berubah lebih sering.

Jenis persistensi lebih mudah diubah, baik basis data atau teknologi untuk mengakses basis data yang sama daripada logika domain atau tindakan yang dirancang untuk berkomunikasi dengan kegigihan. Karena itu, ketergantungan ini dibalik karena lebih mudah untuk mengubah kegigihan jika perubahan ini terjadi. Dengan cara ini kita tidak perlu mengubah domain. Lapisan domain adalah yang paling stabil, oleh karena itu ia tidak boleh bergantung pada apa pun.

Tetapi tidak hanya ada contoh repositori ini. Ada banyak skenario di mana prinsip ini berlaku dan ada arsitektur berdasarkan prinsip ini.

Ilmu bangunan

Ada arsitektur di mana inversi ketergantungan adalah kunci dari definisinya. Di semua domain itu adalah yang paling penting dan itu adalah abstraksi yang akan menunjukkan protokol komunikasi antara domain dan sisa paket atau pustaka didefinisikan.

Arsitektur Bersih

Dalam arsitektur bersih , domain terletak di tengah dan jika Anda melihat ke arah panah yang menunjukkan ketergantungan, jelas lapisan apa yang paling penting dan stabil. Lapisan luar dianggap alat yang tidak stabil jadi hindari tergantung padanya.


(sumber: 8thlight.com )

Arsitektur Heksagonal

Ini terjadi dengan cara yang sama dengan arsitektur heksagonal, di mana domain juga terletak di bagian tengah dan port adalah abstraksi komunikasi dari domino ke arah luar. Di sini sekali lagi terbukti bahwa domain adalah ketergantungan yang paling stabil dan tradisional dibalik.

xurxodev
sumber
Saya tidak yakin apa yang ingin dikatakan, tetapi kalimat ini tidak jelas: "Tetapi menurut pendekatan yang kami ambil, kami bisa salah mengaplikasikan ketergantungan pada investasi, tetapi abstraksi." Mungkin Anda ingin memperbaikinya?
Mark Amery
9

Bagi saya, Prinsip Ketergantungan Inversi, sebagaimana dijelaskan dalam artikel resmi , benar-benar merupakan upaya yang salah arah untuk meningkatkan usabilitas modul yang secara inheren kurang dapat digunakan kembali, serta cara untuk mengatasi masalah dalam bahasa C ++.

Masalah dalam C ++ adalah bahwa file header biasanya berisi deklarasi bidang pribadi dan metode. Oleh karena itu, jika modul C ++ tingkat tinggi menyertakan file header untuk modul tingkat rendah, itu akan tergantung pada detail implementasi aktual dari modul itu. Dan itu, jelas, bukan hal yang baik. Tapi ini bukan masalah dalam bahasa yang lebih modern yang umum digunakan saat ini.

Modul tingkat tinggi secara inheren kurang dapat digunakan kembali daripada modul tingkat rendah karena yang pertama biasanya lebih spesifik aplikasi / konteks daripada yang terakhir. Sebagai contoh, komponen yang mengimplementasikan layar UI adalah tingkat tertinggi dan juga sangat (sepenuhnya?) Khusus untuk aplikasi. Mencoba menggunakan kembali komponen seperti itu dalam aplikasi yang berbeda adalah kontra-produktif, dan hanya dapat menyebabkan rekayasa berlebihan.

Jadi, pembuatan abstraksi terpisah pada tingkat yang sama dari komponen A yang bergantung pada komponen B (yang tidak bergantung pada A) dapat dilakukan hanya jika komponen A benar-benar akan berguna untuk digunakan kembali dalam aplikasi atau konteks yang berbeda. Jika itu tidak terjadi, maka menerapkan DIP akan menjadi desain yang buruk.

Rogério
sumber
Bahkan jika abstraksi tingkat tinggi hanya berguna dalam konteks aplikasi itu, ia dapat memiliki nilai ketika Anda ingin mengganti implementasi nyata dengan tulisan rintisan untuk pengujian (atau menyediakan antarmuka baris perintah ke aplikasi yang biasanya tersedia di web)
Przemek Pokrywka
3
DIP penting di berbagai bahasa dan tidak ada hubungannya dengan C ++ itu sendiri. Bahkan jika kode tingkat tinggi Anda tidak akan pernah meninggalkan aplikasi Anda, DIP memungkinkan Anda untuk mengandung perubahan kode ketika Anda menambah atau mengubah kode tingkat yang lebih rendah. Ini mengurangi biaya perawatan dan konsekuensi perubahan yang tidak diinginkan. DIP adalah konsep tingkat yang lebih tinggi. Jika Anda tidak memahaminya, Anda perlu melakukan lebih banyak googling.
Dirk Bester
3
Saya pikir Anda salah mengerti apa yang saya katakan tentang C ++. Itu hanya motivasi untuk DIP; tidak diragukan lagi itu lebih umum dari itu. Perhatikan artikel resmi tentang DIP memperjelas motivasi utama adalah untuk mendukung penggunaan kembali modul tingkat tinggi, dengan membuatnya kebal terhadap perubahan dalam modul tingkat rendah; tanpa perlu digunakan kembali, maka sangat mungkin akan membutuhkan banyak dan banyak rekayasa. (Apakah Anda membacanya? Ini juga berbicara tentang masalah C ++.)
Rogério
1
Apakah Anda tidak mengabaikan aplikasi terbalik DIP? Artinya, modul tingkat yang lebih tinggi mengimplementasikan aplikasi Anda, pada dasarnya. Perhatian utama Anda bukan untuk menggunakannya kembali, itu untuk membuatnya kurang tergantung pada implementasi modul tingkat rendah sehingga memperbaruinya untuk mengimbangi perubahan di masa depan mengisolasi aplikasi Anda dari kerusakan waktu dan teknologi baru. Ini bukan untuk membuatnya lebih mudah untuk menggantikan WindowsOS, itu untuk membuat WindowsOS kurang tergantung pada detail implementasi FAT / HDD sehingga NTFS / SSD yang lebih baru dapat dicolokkan ke WindowsOS tanpa dampak atau sedikit dampak.
knockNrod
1
Layar UI jelas bukan modul tingkat tertinggi atau tidak boleh untuk aplikasi apa pun. Modul tingkat tertinggi adalah modul yang mengandung aturan bisnis. Modul level tertinggi tidak ditentukan oleh potensinya untuk dapat digunakan kembali juga. Silakan periksa penjelasan sederhana Paman Bob di artikel ini: blog.cleancoder.com/uncle-bob/2016/01/04/…
humbaba
8

Pada dasarnya dikatakan:

Kelas harus bergantung pada abstraksi (mis. Antarmuka, kelas abstrak), bukan detail spesifik (implementasi).

martin.ra
sumber
Mungkinkah sesederhana itu? Ada artikel panjang dan bahkan buku seperti yang disebutkan DerekGreer? Saya sebenarnya sedang mencari jawaban yang sederhana, tetapi tidak dapat dipercaya jika sesederhana itu: D
Darius.V
1
@ darius-v itu bukan versi lain dari mengatakan "gunakan abstraksi". Ini tentang siapa yang memiliki antarmuka. Prinsipal mengatakan klien (komponen tingkat lebih tinggi) harus mendefinisikan antarmuka dan komponen tingkat bawah harus mengimplementasikannya.
Boran
6

Jawaban yang baik dan contoh yang baik sudah diberikan oleh orang lain di sini.

Alasan DIP penting adalah karena memastikan prinsip OO "desain yang digabungkan secara longgar".

Objek dalam perangkat lunak Anda TIDAK boleh masuk ke hierarki di mana beberapa objek adalah yang tingkat atas, tergantung pada objek tingkat rendah. Perubahan objek tingkat rendah kemudian akan beriak ke objek tingkat atas Anda yang membuat perangkat lunak sangat rapuh untuk perubahan.

Anda ingin objek 'tingkat atas' Anda menjadi sangat stabil dan tidak rapuh untuk perubahan, oleh karena itu Anda perlu membalikkan dependensi.

Hace
sumber
6
Bagaimana sebenarnya DIP melakukan itu, untuk kode Java atau .NET? Dalam bahasa-bahasa itu, bertentangan dengan C ++, perubahan implementasi modul tingkat rendah tidak memerlukan perubahan pada modul tingkat tinggi yang menggunakannya. Hanya perubahan di antarmuka publik yang akan beriak, tetapi abstraksi yang ditentukan di tingkat yang lebih tinggi juga harus berubah.
Rogério
5

Cara yang jauh lebih jelas untuk menyatakan Prinsip Ketergantungan Inversi adalah:

Modul Anda yang merangkum logika bisnis yang kompleks tidak harus bergantung langsung pada modul lain yang merangkum logika bisnis. Sebaliknya, mereka harusnya hanya bergantung pada antarmuka untuk data sederhana.

Yaitu, alih-alih mengimplementasikan kelas Anda Logicseperti yang biasa dilakukan orang:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

Anda harus melakukan sesuatu seperti:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Datadan DataFromDependencyharus hidup dalam modul yang sama dengan Logic, bukan dengan Dependency.

Kenapa melakukan ini?

  1. Dua modul logika bisnis sekarang dipisahkan. Saat Dependencyberubah, Anda tidak perlu berubah Logic.
  2. Memahami apa yang Logicdilakukan adalah tugas yang jauh lebih sederhana: itu hanya beroperasi pada apa yang tampak seperti ADT.
  3. Logicsekarang bisa lebih mudah diuji. Anda sekarang dapat langsung instantiate Datadengan data palsu dan meneruskannya. Tidak perlu mengejek atau perancah tes kompleks.
mattvonb
sumber
Saya pikir ini tidak benar. Jika DataFromDependency, yang secara langsung merujuk Dependency, ada dalam modul yang sama dengan Logic, maka Logicmodul masih secara langsung tergantung pada Dependencymodul pada waktu kompilasi. Per penjelasan Paman Bob tentang prinsip , menghindari itu adalah inti dari DIP. Sebaliknya, untuk mengikuti DIP, Dataharus dalam modul yang sama dengan Logic, tetapi DataFromDependencyharus dalam modul yang sama dengan Dependency.
Mark Amery
3

Inversion of control (IoC) adalah pola desain di mana objek diserahkan ketergantungannya oleh kerangka luar, daripada meminta kerangka kerja untuk ketergantungannya.

Contoh pseudocode menggunakan pencarian tradisional:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

Kode serupa menggunakan IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

Manfaat IoC adalah:

  • Anda tidak memiliki ketergantungan pada kerangka kerja pusat, jadi ini dapat diubah jika diinginkan.
  • Karena objek dibuat dengan injeksi, lebih disukai menggunakan antarmuka, mudah untuk membuat tes unit yang menggantikan dependensi dengan versi tiruan.
  • Memisahkan kode off.
Staale
sumber
Apakah Prinsip Ketergantungan Pembalikan dan Pembalikan Pengendalian adalah hal yang sama?
Peter Mortensen
3
"Inversi" di DIP tidak sama dengan di Inversion of Control. Yang pertama adalah tentang dependensi kompilasi-waktu, sedangkan yang kedua adalah tentang aliran kontrol antara metode saat runtime.
Rogério
Saya merasa versi IoC kehilangan sesuatu. Bagaimana objek Database itu didefinisikan, dari mana asalnya. Saya mencoba untuk memahami bagaimana ini tidak hanya menunda menentukan semua ketergantungan ke tingkat atas dalam ketergantungan dewa raksasa
Richard Tingle
1
Seperti yang sudah dikatakan oleh @ Rogério, DIP bukan DI / IoC. Jawaban ini salah IMO
zeraDev
1

Titik inversi ketergantungan adalah membuat perangkat lunak yang dapat digunakan kembali.

Idenya adalah bahwa alih-alih dua potong kode mengandalkan satu sama lain, mereka bergantung pada beberapa antarmuka abstrak. Kemudian Anda dapat menggunakan kembali salah satu bagian tanpa yang lain.

Cara ini paling umum dicapai adalah melalui wadah inversi kontrol (IoC) seperti Spring di Jawa. Dalam model ini, properti objek diatur melalui konfigurasi XML alih-alih objek keluar dan menemukan ketergantungannya.

Bayangkan kodesemu ini ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass secara langsung tergantung pada kelas Layanan dan kelas ServiceLocator. Ini membutuhkan keduanya jika Anda ingin menggunakannya di aplikasi lain. Sekarang bayangkan ini ...

public class MyClass
{
  public IService myService;
}

Sekarang, MyClass bergantung pada antarmuka tunggal, antarmuka IService. Kami membiarkan wadah IoC benar-benar mengatur nilai variabel itu.

Jadi sekarang, MyClass dapat dengan mudah digunakan kembali dalam proyek-proyek lain, tanpa membawa ketergantungan dua kelas lainnya dengannya.

Lebih baik lagi, Anda tidak perlu menyeret dependensi MyService, dan dependensi dependensi itu, dan ... yah, Anda mendapatkan idenya.

Marc Hughes
sumber
2
Jawaban ini sebenarnya bukan tentang DIP, tetapi sesuatu yang lain. Dua buah kode dapat bergantung pada beberapa antarmuka yang diabstraksi dan tidak dalam satu sama lain, dan masih tidak mengikuti Prinsip Ketergantungan Inversi.
Rogério
1

Jika kita dapat menganggapnya sebagai suatu pemberian bahwa seorang karyawan "tingkat tinggi" di suatu perusahaan dibayar untuk pelaksanaan rencana mereka, dan bahwa rencana ini disampaikan oleh eksekusi agregat dari banyak rencana karyawan "tingkat rendah", maka kita dapat mengatakan umumnya merupakan rencana yang mengerikan jika uraian rencana karyawan tingkat tinggi dengan cara apa pun digabungkan dengan rencana spesifik karyawan tingkat bawah mana pun.

Jika seorang eksekutif tingkat tinggi memiliki rencana untuk "meningkatkan waktu pengiriman", dan menunjukkan bahwa seorang karyawan di jalur pelayaran harus minum kopi dan melakukan peregangan setiap pagi, maka rencana itu sangat berpasangan dan memiliki kohesi yang rendah. Tetapi jika rencana tersebut tidak menyebutkan karyawan tertentu, dan pada kenyataannya hanya membutuhkan "entitas yang dapat melakukan pekerjaan siap untuk bekerja", maka rencana tersebut digabungkan secara longgar dan lebih kohesif: rencana tidak tumpang tindih dan dapat dengan mudah diganti . Kontraktor, atau robot, dapat dengan mudah mengganti karyawan dan rencana tingkat tinggi tetap tidak berubah.

"Tingkat tinggi" dalam prinsip inversi ketergantungan berarti "lebih penting".

mike
sumber
1
Ini adalah analogi yang sangat bagus. Sama seperti, di bawah DIC, komponen tingkat tinggi masih bergantung pada runtime pada yang tingkat rendah, dalam analogi ini, pelaksanaan rencana tingkat tinggi tergantung pada rencana tingkat rendah. Tetapi juga, seperti halnya di bawah DIC, rencana tingkat rendah yang bergantung pada waktu kompilasi pada rencana tingkat tinggi, dalam analogi Anda, pembentukan rencana tingkat rendah bergantung pada rencana tingkat tinggi.
Mark Amery
0

Saya bisa melihat penjelasan yang baik telah diberikan dalam jawaban di atas. Namun saya ingin memberikan beberapa penjelasan mudah dengan contoh sederhana.

Dependency Inversion Principle memungkinkan programmer untuk menghapus dependensi hardcoded sehingga aplikasi menjadi longgar dan dapat diperpanjang.

Cara mencapai ini: melalui abstraksi

Tanpa inversi ketergantungan:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

Dalam cuplikan kode di atas, objek alamat diberi kode keras. Alih-alih jika kita dapat menggunakan invensi ketergantungan dan menyuntikkan objek alamat dengan melewati konstruktor atau metode penyetel. Ayo lihat.

Dengan inversi ketergantungan:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}
Sumanth Varada
sumber
-1

Katakanlah kita memiliki dua kelas: Engineerdan Programmer:

Engineer Kelas memiliki ketergantungan pada kelas Programmer, Seperti di bawah ini:

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

Pada contoh ini kelas Engineermemiliki ketergantungan pada Programmerkelas kami . Apa yang akan terjadi jika saya perlu mengubah Programmer?

Jelas saya perlu mengubahnya Engineerjuga. (Wow, pada titik OCPini juga dilanggar)

Lalu, Apa yang harus kita bersihkan dari kekacauan ini? Jawabannya adalah abstraksi sebenarnya. Dengan abstraksi, kita dapat menghapus ketergantungan antara dua kelas ini. Sebagai contoh, saya dapat membuat sebuah Interfaceuntuk kelas Programmer dan mulai sekarang setiap kelas yang ingin menggunakan Programmerharus menggunakan Interface, Kemudian dengan mengubah kelas Programmer, kita tidak perlu mengubah kelas yang menggunakannya, Karena kami abstraksi bekas.

Catatan: DependencyInjectiondapat membantu kita untuk melakukan DIPdan SRPjuga.

Ehsan
sumber
-1

Menambahkan ke kebingungan jawaban yang umumnya baik, saya ingin menambahkan sampel kecil saya sendiri untuk menunjukkan praktik yang baik vs buruk. Dan ya, saya bukan orang yang melempar batu!

Katakanlah, Anda ingin sedikit program untuk mengubah string ke format base64 melalui konsol I / O. Inilah pendekatan naif:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

DIP pada dasarnya mengatakan bahwa komponen tingkat tinggi tidak boleh bergantung pada implementasi tingkat rendah, di mana "level" adalah jarak dari I / O menurut Robert C. Martin ("Clean Architecture"). Tetapi bagaimana Anda bisa keluar dari kesulitan ini? Cukup dengan membuat Encoder pusat hanya bergantung pada antarmuka tanpa mengganggu cara penerapannya:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

Perhatikan bahwa Anda tidak perlu menyentuh GoodEncoderuntuk mengubah mode I / O - kelas itu senang dengan antarmuka I / O yang diketahuinya; implementasi tingkat rendah IReadabledan IWriteabletidak akan pernah mengganggunya.

John Silence
sumber
Saya tidak berpikir ini adalah Ketergantungan Inversi. Bagaimanapun, Anda belum membalikkan ketergantungan apa pun di sini; pembaca dan penulis Anda tidak bergantung pada GoodEncodercontoh kedua Anda. Untuk membuat contoh DIP, Anda perlu memperkenalkan gagasan tentang apa yang "memiliki" antarmuka yang telah Anda ekstrak di sini - dan, khususnya, untuk meletakkannya dalam paket yang sama dengan GoodEncoder sementara implementasinya tetap berada di luar.
Mark Amery
-2

Prinsip Ketergantungan Inversi (DIP) mengatakan itu

i) Modul tingkat tinggi tidak boleh bergantung pada modul tingkat rendah. Keduanya harus bergantung pada abstraksi.

ii) Abstraksi tidak boleh bergantung pada detail. Detail harus bergantung pada abstraksi.

Contoh:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

Catatan: Kelas harus bergantung pada abstraksi seperti antarmuka atau kelas abstrak, bukan detail spesifik (implementasi antarmuka).

Rejwanul Reja
sumber