Saat ini saya mencoba mencari tahu SOLID. Jadi Prinsip Ketergantungan Inversi berarti bahwa dua kelas harus berkomunikasi melalui antarmuka, bukan secara langsung. Contoh: Jika class A
memiliki metode, yang mengharapkan pointer ke objek bertipe class B
, maka metode ini seharusnya benar-benar mengharapkan objek bertipe abstract base class of B
. Ini membantu untuk Buka / Tutup juga.
Asalkan saya mengerti dengan benar, pertanyaan saya adalah apakah itu praktik yang baik untuk menerapkan ini pada semua interaksi kelas atau haruskah saya mencoba berpikir dalam hal lapisan ?
Alasan saya skeptis adalah karena kami membayar sejumlah harga untuk mengikuti prinsip ini. Katakanlah, saya perlu mengimplementasikan fitur Z
. Setelah analisis, saya menyimpulkan bahwa fitur Z
terdiri dari fungsionalitas A
, B
dan C
. Saya membuat kelas fasadZ
, yang, melalui antarmuka, menggunakan kelas A
, B
dan C
. Saya mulai mengkode implementasi dan pada titik tertentu saya menyadari bahwa tugas Z
sebenarnya terdiri dari fungsionalitas A
, B
dan D
. Sekarang saya perlu memo C
antarmuka, C
prototipe kelas dan menulis D
antarmuka dan kelas terpisah . Tanpa antarmuka, hanya kelas yang perlu diganti.
Dengan kata lain, untuk mengubah sesuatu, saya perlu mengubah 1. penelepon 2. antarmuka 3. deklarasi 4. implementasinya. Dalam python langsung digabungkan implementasi, saya akan perlu mengubah hanya pelaksanaannya.
Jawaban:
Dalam banyak kartun atau media lain, kekuatan baik dan jahat sering diilustrasikan oleh malaikat dan iblis yang duduk di bahu karakter. Dalam cerita kami di sini, bukannya kebaikan dan kejahatan, kami memiliki SOLID di satu bahu, dan YAGNI (Anda tidak akan membutuhkannya!) Duduk di sisi lain.
Prinsip SOLID yang diambil secara maksimal paling sesuai untuk sistem perusahaan yang besar, kompleks, dan sangat dapat dikonfigurasi. Untuk sistem yang lebih kecil, atau lebih spesifik, tidak tepat untuk membuat semuanya sangat fleksibel, karena waktu yang Anda habiskan untuk mengabstraksi sesuatu tidak akan terbukti bermanfaat.
Melewati antarmuka alih-alih kelas konkret terkadang berarti misalnya Anda dapat dengan mudah bertukar membaca dari file untuk aliran jaringan. Namun, untuk sejumlah besar proyek perangkat lunak, fleksibilitas semacam itu tidak akan pernah dibutuhkan, dan Anda mungkin juga hanya lulus kelas file konkret dan menyebutnya sehari dan luangkan sel-sel otak Anda.
Bagian dari seni pengembangan perangkat lunak adalah memiliki perasaan yang baik tentang apa yang mungkin berubah seiring berjalannya waktu, dan apa yang tidak. Untuk hal-hal yang cenderung berubah, gunakan antarmuka dan konsep SOLID lainnya. Untuk hal-hal yang tidak akan, gunakan YAGNI dan hanya lulus jenis beton, lupakan kelas pabrik, lupakan semua runtime yang terhubung dan konfigurasi, dll, dan lupakan banyak abstraksi SOLID. Dalam pengalaman saya, pendekatan YAGNI terbukti benar jauh lebih sering daripada tidak.
sumber
File
, jadi alih-alih itu akan membutuhkanIFile
, pekerjaan yang dilakukan". Kemudian mereka tidak dapat dengan mudah mengganti aliran jaringan, karena mereka terlalu banyak menuntut antarmuka, dan ada operasi dalamIFile
metode bahkan tidak menggunakan, yang tidak berlaku untuk soket, sehingga soket tidak dapat diimplementasikanIFile
. Salah satu hal yang DI bukan peluru perak, adalah menciptakan abstraksi yang tepat (interface) :-)Dalam kata-kata awam:
Menerapkan DIP itu mudah dan menyenangkan . Tidak mendapatkan desain yang benar pada upaya pertama tidak cukup alasan menyerah pada DIP sama sekali.
Di sisi lain, pemrograman dengan antarmuka dan OOD dapat membawa sukacita kembali ke keahlian pemrograman yang terkadang basi.
Beberapa orang mengatakan ini menambah kompleksitas tetapi saya pikir situs oposite itu benar. Bahkan untuk proyek kecil. Itu membuat pengujian / mengejek lebih mudah. Itu membuat kode Anda memiliki lebih sedikit jika ada
case
pernyataan atau bersarangifs
. Ini mengurangi kompleksitas siklomatik dan membuat Anda berpikir dengan cara yang segar. Itu membuat pemrograman lebih mirip dengan desain dan manufaktur dunia nyata.sumber
Gunakan inversi ketergantungan yang masuk akal.
Satu contoh tandingan ekstrim adalah kelas "string" yang disertakan dalam banyak bahasa. Ini mewakili konsep primitif, pada dasarnya array karakter. Dengan asumsi Anda dapat mengubah kelas inti ini, tidak masuk akal untuk menggunakan DI di sini karena Anda tidak perlu menukar keadaan internal dengan sesuatu yang lain.
Jika Anda memiliki sekelompok objek yang digunakan secara internal dalam modul yang tidak terpapar ke modul lain atau digunakan kembali di mana saja, mungkin tidak ada gunanya upaya untuk menggunakan DI.
Ada dua tempat di mana DI seharusnya digunakan secara otomatis menurut saya:
Dalam modul yang dirancang untuk ekstensi. Jika seluruh tujuan modul adalah untuk memperpanjangnya dan mengubah perilaku, masuk akal untuk memanggang DI sejak awal.
Dalam modul yang Anda refactoring untuk tujuan penggunaan kembali kode. Mungkin Anda memberi kode pada sebuah kelas untuk melakukan sesuatu, kemudian menyadari kemudian bahwa dengan refactor Anda dapat memanfaatkan kode itu di tempat lain dan ada kebutuhan untuk melakukannya . Itu adalah kandidat yang bagus untuk DI dan perubahan ekstensibilitas lainnya.
Kunci-kunci di sini digunakan di tempat yang diperlukan karena akan memperkenalkan kompleksitas tambahan, dan pastikan Anda mengukur kebutuhan itu baik melalui persyaratan teknis (poin satu) atau tinjauan kode kuantitatif (poin dua).
DI adalah alat yang hebat, tetapi sama seperti alat *, itu bisa digunakan secara berlebihan atau disalahgunakan.
* Pengecualian terhadap aturan di atas: gergaji balasan adalah alat yang sempurna untuk pekerjaan apa pun. Jika tidak memperbaiki masalah Anda, itu akan menghapusnya. Secara permanen.
sumber
String
, ada banyak kasus di mana representasi alternatif akan membantu jika jenis memiliki rangkaian operasi virtual yang baik (misalnya menyalin substring ke bagian tertentu dari ashort[]
, melaporkan apakah substring tidak atau mungkin hanya mengandung ASCII, coba salin substring yang diyakini hanya mengandung ASCII ke bagian tertentu daribyte[]
, dll.) Kerangka kerja buruknya tidak memiliki tipe string mereka yang mengimplementasikan antarmuka terkait string yang berguna.Tampak bagi saya bahwa pertanyaan awal hilang bagian dari titik DIP.
Untuk benar-benar mengambil keuntungan dari DIP, Anda harus membuat kelas Z terlebih dahulu, dan menyebutnya fungsi kelas A, B dan C (yang belum dikembangkan). Ini memberi Anda API untuk kelas A, B dan C. Kemudian Anda pergi dan membuat kelas A, B dan C dan isi detailnya. Anda harus secara efektif membuat abstraksi yang Anda butuhkan saat Anda membuat kelas Z, berdasarkan sepenuhnya pada apa yang dibutuhkan kelas Z. Anda bahkan dapat menulis tes di sekitar kelas Z sebelum kelas A, B atau C bahkan ditulis.
Ingat bahwa DIP mengatakan bahwa "Modul tingkat tinggi seharusnya tidak bergantung pada modul tingkat rendah. Keduanya harus bergantung pada abstraksi."
Setelah Anda mengetahui apa yang dibutuhkan kelas Z dan cara yang diinginkannya untuk mendapatkan apa yang dibutuhkan, Anda dapat mengisi detailnya. Tentu, terkadang perubahan harus dilakukan ke kelas Z, tetapi 99% dari waktu ini tidak akan menjadi masalah.
Tidak akan pernah ada kelas D karena Anda mengetahui bahwa Z membutuhkan A, B dan C sebelum ditulis. Perubahan persyaratan adalah cerita yang berbeda sama sekali.
sumber
Jawaban singkatnya adalah "hampir tidak pernah", tetapi sebenarnya ada beberapa tempat di mana DIP tidak masuk akal:
Pabrik atau pembangun, yang tugasnya menciptakan objek. Ini pada dasarnya adalah "leaf node" dalam suatu sistem yang sepenuhnya merangkul IoC. Pada titik tertentu, sesuatu harus benar-benar membuat objek Anda, dan tidak dapat bergantung pada hal lain untuk melakukan itu. Dalam banyak bahasa, wadah IoC dapat melakukannya untuk Anda, tetapi kadang-kadang Anda perlu melakukannya dengan cara kuno.
Implementasi struktur data dan algoritma. Secara umum, dalam kasus ini karakteristik penting yang Anda optimalkan (seperti waktu berjalan dan kompleksitas asimptotik) bergantung pada tipe data spesifik yang digunakan. Jika Anda menerapkan tabel hash, Anda benar-benar perlu tahu bahwa Anda bekerja dengan array untuk penyimpanan, bukan daftar yang ditautkan, dan hanya tabel itu sendiri yang tahu cara mengalokasikan array dengan benar. Anda juga tidak ingin meneruskan dalam array yang bisa berubah dan meminta penelepon memecahkan tabel hash Anda dengan mengutak-atik isinya.
Kelas model domain . Ini menerapkan logika bisnis Anda, dan (sebagian besar waktu) hanya masuk akal untuk memiliki satu implementasi, karena (sebagian besar waktu) Anda hanya mengembangkan perangkat lunak untuk satu bisnis. Meskipun beberapa kelas model domain dapat dibangun menggunakan kelas model domain lain, ini umumnya akan didasarkan pada kasus per kasus. Karena objek model domain tidak menyertakan fungsionalitas yang dapat diejek dengan bermanfaat, tidak ada manfaat testability atau rawatan untuk DIP.
Objek apa pun yang disediakan sebagai API eksternal dan perlu membuat objek lain, yang detail implementasinya tidak ingin Anda ungkapkan kepada publik. Ini termasuk dalam kategori umum "desain perpustakaan berbeda dari desain aplikasi". Perpustakaan atau kerangka kerja dapat menggunakan DI secara liberal, tetapi pada akhirnya harus melakukan beberapa pekerjaan yang sebenarnya, kalau tidak itu bukan perpustakaan yang sangat berguna. Katakanlah Anda sedang mengembangkan perpustakaan jaringan; Anda benar-benar tidak ingin konsumen dapat memberikan implementasi soketnya sendiri. Anda mungkin secara internal menggunakan abstraksi soket, tetapi API yang Anda paparkan kepada penelepon akan membuat soketnya sendiri.
Tes unit, dan tes ganda. Palsu dan bertopik seharusnya melakukan satu hal dan melakukannya dengan sederhana. Jika Anda memiliki palsu yang cukup rumit untuk dikhawatirkan apakah perlu atau tidak melakukan injeksi ketergantungan, maka itu mungkin terlalu rumit (mungkin karena itu mengimplementasikan antarmuka yang juga terlalu rumit).
Mungkin ada lebih banyak; ini yang saya lihat agak sering.
sumber
Beberapa tanda Anda mungkin menerapkan DIP pada tingkat yang terlalu mikro, di mana itu tidak memberikan nilai:
Jika ini yang Anda lihat, Anda mungkin lebih baik hanya memiliki panggilan Z secara langsung dan melewati antarmuka.
Juga, saya tidak memikirkan metode dekorasi dengan injeksi dependensi / kerangka kerja proksi dinamis (Spring, Java EE) dengan cara yang sama seperti DIP SOLID yang sebenarnya - ini lebih seperti detail implementasi tentang cara kerja metode dekorasi di tumpukan teknologi itu. Komunitas Java EE menganggapnya sebagai peningkatan yang Anda tidak perlu pasang Foo / FooImpl seperti dulu ( referensi ). Sebaliknya, Python mendukung dekorasi fungsi sebagai fitur bahasa kelas satu.
Lihat juga posting blog ini .
sumber
Jika Anda selalu membalikkan dependensi Anda, maka semua dependensi Anda terbalik. Yang berarti bahwa jika Anda mulai dengan kode berantakan dengan simpul dependensi, itulah yang masih (benar-benar) miliki, hanya terbalik. Di situlah Anda mendapatkan masalah yang perlu diubah setiap antarmuka untuk mengubah antarmuka.
Titik inversi ketergantungan adalah Anda secara selektif membalikkan dependensi yang membuat berbagai hal menjadi kusut. Yang harus beralih dari A ke B ke C masih tetap melakukannya, itu yang pergi dari C ke A yang sekarang beralih dari A ke C.
Hasilnya harus berupa grafik dependensi yang bebas dari siklus - DAG. Ada berbagai alat yang akan memeriksa properti ini, dan menggambar grafik.
Untuk penjelasan lebih lengkap, lihat artikel ini :
sumber