Saya punya pertanyaan sederhana, dan saya bahkan tidak yakin punya jawaban tetapi mari kita coba. Saya mengkode dalam C ++, dan menggunakan injeksi ketergantungan untuk menghindari keadaan global. Ini bekerja dengan sangat baik, dan saya tidak sering menjalankan perilaku yang tidak terduga / tidak terdefinisi.
Namun saya menyadari bahwa, seiring pertumbuhan proyek saya, saya menulis banyak kode yang saya anggap boilerplate. Lebih buruk: fakta bahwa ada lebih banyak kode boilerplate, daripada kode aktual membuatnya terkadang sulit untuk dipahami.
Tidak ada yang mengalahkan contoh yang baik jadi mari kita pergi:
Saya memiliki kelas yang disebut TimeFactory yang menciptakan objek Time.
Untuk perincian lebih lanjut (tidak yakin itu relevan): Objek waktu cukup kompleks karena Waktu dapat memiliki format yang berbeda, dan konversi di antara keduanya tidak linier, maupun langsung. Setiap "Waktu" berisi Sinkronisasi untuk menangani konversi, dan untuk memastikan mereka memiliki sinkronisasi yang sama, diinisialisasi dengan benar, saya menggunakan TimeFactory. The TimeFactory hanya memiliki satu instance dan aplikasi yang luas, sehingga akan memenuhi syarat untuk singleton tetapi, karena itu bisa berubah, saya tidak ingin menjadikannya singleton
Di aplikasi saya, banyak kelas perlu membuat objek Waktu. Terkadang kelas-kelas itu sangat bersarang.
Katakanlah saya memiliki kelas A yang berisi instance kelas B, dan seterusnya hingga kelas D. Kelas D perlu membuat objek Waktu.
Dalam implementasi saya yang naif, saya meneruskan TimeFactory ke konstruktor kelas A, yang meneruskannya ke konstruktor kelas B dan seterusnya hingga kelas D.
Sekarang, bayangkan saya memiliki beberapa kelas seperti TimeFactory dan beberapa hierarki kelas seperti yang di atas: Saya kehilangan semua fleksibilitas dan keterbacaan yang saya anggap menggunakan injeksi ketergantungan.
Saya mulai bertanya-tanya apakah tidak ada cacat desain utama di aplikasi saya ... Atau apakah ini kejahatan yang diperlukan dalam menggunakan injeksi ketergantungan?
Apa yang kamu pikirkan ?
sumber
Jawaban:
Tampaknya
Time
kelas Anda adalah tipe data yang sangat mendasar yang seharusnya menjadi bagian dari "infrastruktur umum" aplikasi Anda. DI tidak berfungsi dengan baik untuk kelas seperti itu. Pikirkan apa artinya jika kelas sepertistring
harus disuntikkan ke setiap bagian dari kode yang menggunakan string, dan Anda akan perlu menggunakanstringFactory
sebagai satu-satunya kemungkinan membuat string baru - keterbacaan program Anda akan berkurang dengan urutan besarnya.Jadi saran saya: jangan gunakan DI untuk tipe data umum seperti
Time
. Tulis tes unit untukTime
kelas itu sendiri, dan ketika selesai, gunakan itu di mana-mana dalam program Anda, sepertistring
kelas, atauvector
kelas atau kelas lain dari lib standar. Gunakan DI untuk komponen yang harus benar-benar dipisahkan satu sama lain.sumber
Time
dan bagian lain dari program Anda. Jadi Anda bisa menerima juga menerima kopling ketatTimeFactory
. Namun, yang akan saya hindari adalah memiliki satuTimeFactory
objek global dengan status (misalnya, informasi lokal atau sesuatu seperti itu) - yang dapat menyebabkan efek samping buruk pada program Anda dan membuat pengujian umum dan penggunaan ulang sangat sulit. Entah membuatnya tanpa kewarganegaraan atau tidak menggunakannya sebagai tunggal.Time
danTimeFactory
, pada tingkat "evolvabilitas" yang Anda butuhkan untuk ekstensi di masa depan terkait denganTimeFactory
, dan seterusnya.Apa yang Anda maksud dengan "Saya kehilangan semua fleksibilitas dan keterbacaan yang saya kira harus menggunakan injeksi ketergantungan" - DI bukan tentang keterbacaan. Ini tentang memisahkan ketergantungan antara objek.
Sepertinya Anda memiliki Kelas A yang menciptakan Kelas B, Kelas B menciptakan Kelas C dan Kelas C menciptakan Kelas D.
Apa yang seharusnya Anda miliki adalah Kelas B disuntikkan ke Kelas A. Kelas C disuntikkan ke Kelas B. Kelas D disuntikkan ke Kelas C.
sumber
Saya tidak yakin mengapa Anda tidak ingin membuat pabrik waktu Anda lajang. Jika hanya ada satu contoh saja di seluruh aplikasi Anda, itu sebenarnya singleton.
Yang sedang berkata, sangat berbahaya untuk berbagi objek yang bisa berubah, kecuali jika itu dijaga dengan baik dengan menyinkronkan blok, dalam hal ini, tidak ada alasan untuk itu tidak menjadi singleton.
Jika Anda ingin melakukan injeksi dependensi, Anda mungkin ingin melihat pegas atau kerangka kerja injeksi dependensi lainnya, yang memungkinkan Anda untuk menetapkan parameter secara otomatis menggunakan anotasi
sumber
Ini bertujuan untuk menjadi jawaban pelengkap bagi Doc Brown, dan juga untuk menanggapi komentar Dinaiz yang belum terjawab yang masih terkait dengan Pertanyaan tersebut.
Apa yang mungkin Anda butuhkan adalah kerangka kerja untuk melakukan DI. Memiliki hierarki yang kompleks tidak harus berarti desain yang buruk, tetapi jika Anda harus menyuntikkan bottom-up TimeFactory (dari A ke D) alih-alih menyuntikkan langsung ke D, maka mungkin ada sesuatu yang salah dengan cara Anda melakukan Injeksi Ketergantungan.
Singleton? Tidak, terima kasih. Jika Anda hanya perlu satu istance untuk membagikannya di seluruh konteks aplikasi Anda (Menggunakan wadah IoC untuk DI seperti Infector ++ hanya perlu untuk mengikat TimeFactory sebagai istance tunggal), berikut ini contohnya (C ++ 11 omong-omong, tapi begitu C ++. Mungkin pindah to C ++ 11 sudah? Anda mendapatkan aplikasi Anti Bocor gratis):
Sekarang poin bagus dari wadah IoC adalah Anda tidak perlu melewatkan waktu pabrik hingga D. jika kelas "D" Anda membutuhkan waktu pabrik, cukup cantumkan pabrik waktu sebagai parameter konstruktor untuk kelas D.
seperti yang Anda lihat Anda menyuntikkan TimeFactory hanya sekali. Bagaimana cara menggunakan "A"? Sangat sederhana, setiap kelas diinjeksi, dibangun di main atau di-istantiated dengan pabrik.
setiap kali Anda membuat kelas A itu akan secara otomatis (lazy istantiation) disuntikkan dengan semua dependensi hingga D dan D akan disuntikkan dengan TimeFactory, jadi dengan memanggil hanya 1 metode, Anda sudah siap dengan hierarki lengkap Anda (dan bahkan hierarki kompleks diselesaikan dengan cara ini) menghapus BANYAK kode pelat ketel): Anda tidak perlu memanggil "baru / hapus" dan itu sangat penting karena Anda dapat memisahkan logika aplikasi dari kode lem.
Itu mudah, TimeFactory Anda memiliki metode "buat", lalu gunakan saja tanda tangan berbeda "buat (params)" dan Anda selesai. Parameter yang tidak ada dependensi sering diselesaikan dengan cara ini. Ini juga menghilangkan tugas menyuntikkan hal-hal seperti "string" atau "bilangan bulat" karena itu hanya menambah pelat ketel tambahan.
Siapa yang menciptakan siapa? Kontainer IoC menciptakan pabrik dan pabrik, pabrik menciptakan sisanya (pabrik dapat membuat objek yang berbeda dengan parameter sewenang-wenang, sehingga Anda tidak benar-benar memerlukan keadaan untuk pabrik). Anda masih dapat menggunakan pabrik sebagai pembungkus untuk Kontainer IoC: secara umum, menyuntikkan Kontainer IoC sangat buruk dan sama dengan menggunakan pencari lokasi layanan. Beberapa orang memecahkan masalah dengan membungkus Kontainer IoC dengan sebuah pabrik (ini tidak sepenuhnya diperlukan, tetapi memiliki keuntungan bahwa hierarki diselesaikan oleh Kontainer dan semua pabrik Anda menjadi lebih mudah dipelihara).
Juga jangan menyalahgunakan injeksi ketergantungan, tipe sederhana hanya bisa menjadi anggota kelas atau variabel cakupan lokal. Ini tampak jelas tetapi saya melihat orang menyuntikkan "std :: vector" hanya karena ada kerangka kerja DI yang memungkinkan. Selalu ingat hukum Demeter: "Suntikkan hanya apa yang benar-benar Anda butuhkan untuk disuntikkan"
sumber
Apakah kelas A, B, dan C Anda juga perlu membuat instance Waktu, atau hanya kelas D? Jika hanya kelas D, maka A dan B seharusnya tidak tahu apa-apa tentang TimeFactory. Membuat instance dari TimeFactory di dalam kelas C, dan meneruskannya ke kelas D. Perhatikan bahwa dengan "membuat sebuah instance" Saya tidak harus berarti bahwa kelas C harus bertanggung jawab untuk membuat instance TimeFactory. Ia dapat menerima DClassFactory dari kelas B, dan DClassFactory tahu cara membuat instance Time.
Teknik yang juga sering saya gunakan ketika saya tidak memiliki kerangka kerja DI adalah menyediakan dua konstruktor, satu yang menerima pabrik, dan satu lagi yang menciptakan pabrik default. Yang kedua biasanya memiliki akses yang dilindungi / paket, dan digunakan terutama untuk pengujian unit.
sumber
Saya menerapkan kerangka kerja injeksi ketergantungan C ++ lainnya, yang baru-baru ini diusulkan untuk meningkatkan - https://github.com/krzysztof-jusiak/di - perpustakaan bersifat makro kurang (gratis), hanya header, C ++ 03 / C ++ 11 / C ++ 14 perpustakaan menyediakan jenis aman, waktu kompilasi, injeksi ketergantungan konstruktor makro gratis.
sumber