Saat mendesain sistem saya sering dihadapkan dengan masalah memiliki banyak modul (logging, akses database, dll) yang digunakan oleh modul lain. Pertanyaannya adalah, bagaimana cara saya menyediakan komponen-komponen ini ke komponen lain. Tampaknya ada dua jawaban ketergantungan injeksi atau menggunakan pola pabrik. Namun keduanya tampak salah:
- Pabrik membuat pengujian menjadi menyakitkan dan tidak memungkinkan pertukaran implementasi yang mudah. Mereka juga tidak membuat dependensi terlihat (misalnya Anda memeriksa suatu metode, tidak menyadari fakta bahwa ia memanggil metode yang memanggil metode yang memanggil metode yang menggunakan database).
- Dependecy injection secara besar-besaran membengkak daftar argumen konstruktor dan itu mengolesi beberapa aspek di seluruh kode Anda. Situasi umum adalah di mana konstruktor lebih dari setengah kelas terlihat seperti ini
(....., LoggingProvider l, DbSessionProvider db, ExceptionFactory d, UserSession sess, Descriptions d)
Berikut adalah situasi umum yang saya punya masalah dengan: Saya memiliki kelas pengecualian, yang menggunakan deskripsi kesalahan yang dimuat dari database, menggunakan kueri yang memiliki parameter pengaturan bahasa pengguna, yang ada di objek sesi pengguna. Jadi untuk membuat Pengecualian baru saya perlu deskripsi, yang membutuhkan sesi basis data dan sesi pengguna. Jadi saya ditakdirkan untuk menyeret semua objek ini di semua metode saya kalau-kalau saya mungkin perlu melemparkan pengecualian.
Bagaimana saya mengatasi masalah seperti itu ??
Jawaban:
Gunakan injeksi ketergantungan, tetapi setiap kali argumen konstruktor Anda menjadi terlalu besar, refactor dengan menggunakan Layanan Fasad . Idenya adalah untuk mengelompokkan beberapa argumen konstruktor bersama, memperkenalkan abstraksi baru.
Misalnya, Anda bisa memperkenalkan jenis baru
SessionEnvironment
mengenkapsulasiDBSessionProvider
, yangUserSession
dan dimuatDescriptions
. Namun, untuk mengetahui abstraksi mana yang paling masuk akal, Anda harus mengetahui detail program Anda.Pertanyaan serupa sudah diajukan di sini pada SO .
sumber
Dari situ, sepertinya Anda tidak memahami DI yang sebenarnya - idenya adalah membalikkan pola instantiasi objek di dalam pabrik.
Masalah khusus Anda tampaknya menjadi masalah OOP yang lebih umum. Mengapa objek tidak bisa hanya melempar pengecualian normal, yang tidak bisa dibaca manusia selama runtime, dan kemudian memiliki sesuatu sebelum percobaan / tangkapan terakhir yang menangkap pengecualian itu, dan pada saat itu menggunakan informasi sesi untuk melempar pengecualian baru yang lebih cantik ?
Pendekatan lain adalah dengan memiliki pabrik pengecualian, yang diteruskan ke objek melalui konstruktor mereka. Alih-alih melempar pengecualian baru, kelas dapat menggunakan metode pabrik (mis
throw PrettyExceptionFactory.createException(data)
.Ingatlah bahwa objek Anda, selain dari objek pabrik Anda, tidak boleh menggunakan
new
operator. Pengecualian pada umumnya merupakan satu kasus khusus, tetapi dalam kasus Anda, mereka mungkin pengecualian!sumber
Builder
polanya) mendikte itu. Jika Anda memberikan parameter ke konstruktor karena objek Anda membuat instance objek lain, itu adalah kasus yang jelas untuk IoC.Anda telah mendaftarkan kerugian dari pola pabrik statis dengan cukup baik, tetapi saya tidak cukup setuju dengan kerugian dari pola injeksi ketergantungan:
Injeksi dependensi mengharuskan Anda untuk menulis kode untuk setiap dependensi bukan bug, tetapi fitur: Ini memaksa Anda untuk berpikir apakah Anda benar-benar membutuhkan dependensi ini, sehingga mempromosikan kopling longgar. Dalam contoh Anda:
Tidak, Anda tidak dikutuk. Mengapa logika bisnis bertanggung jawab untuk melokalkan pesan kesalahan Anda untuk sesi pengguna tertentu? Bagaimana jika, suatu saat nanti, Anda ingin menggunakan layanan bisnis itu dari program batch (yang tidak memiliki sesi pengguna ...)? Atau bagaimana jika pesan kesalahan tidak ditampilkan kepada pengguna yang sedang masuk, tetapi pengawasnya (yang mungkin lebih suka bahasa yang berbeda)? Atau bagaimana jika Anda ingin menggunakan kembali logika bisnis pada klien (yang tidak memiliki akses ke database ...)?
Jelas, pelokalan pesan tergantung pada siapa yang melihat pesan-pesan ini, yaitu itu adalah tanggung jawab dari lapisan presentasi. Oleh karena itu, saya akan membuang pengecualian biasa dari layanan bisnis, yang kebetulan membawa pengenal pesan yang kemudian dapat mencari penangan pengecualian lapisan presentasi di sumber pesan apa pun yang kebetulan digunakan.
Dengan begitu, Anda dapat menghapus 3 dependensi yang tidak perlu (UserSession, ExceptionFactory, dan mungkin deskripsi), sehingga membuat kode Anda lebih sederhana dan lebih fleksibel.
Secara umum, saya hanya akan menggunakan pabrik statis untuk hal-hal yang Anda perlukan aksesnya di mana-mana, dan yang dijamin akan tersedia di semua lingkungan yang kami inginkan untuk menjalankan kode (seperti Logging). Untuk yang lainnya, saya akan menggunakan injeksi ketergantungan lama biasa.
sumber
Gunakan injeksi ketergantungan. Menggunakan pabrik statis adalah pekerjaan
Service Locator
antipattern. Lihat karya mani dari Martin Fowler di sini - http://martinfowler.com/articles/injection.htmlJika argumen konstruktor Anda menjadi terlalu besar dan Anda tidak menggunakan wadah DI dengan cara apa pun menulis pabrik Anda sendiri untuk instantiation, memungkinkannya untuk dapat dikonfigurasi, baik dengan XML atau mengikat implementasi ke antarmuka.
sumber
Saya akan pergi juga dengan Injeksi Ketergantungan. Ingatlah bahwa DI tidak hanya dilakukan melalui konstruktor, tetapi juga melalui setter Properti. Misalnya, logger bisa disuntikkan sebagai properti.
Juga, Anda mungkin ingin menggunakan wadah IoC yang dapat mengangkat beberapa beban untuk Anda, misalnya dengan menjaga parameter konstruktor untuk hal-hal yang diperlukan saat runtime oleh logika domain Anda (menjaga konstruktor dengan cara yang mengungkapkan maksud dari kelas dan dependensi domain sebenarnya) dan mungkin menyuntikkan kelas pembantu lainnya melalui properti.
Satu langkah lebih jauh yang mungkin ingin Anda tuju adalah Programmnig Berorientasi Aspek, yang diimplementasikan dalam banyak kerangka kerja utama. Ini dapat memungkinkan Anda untuk mencegat (atau "menyarankan" untuk menggunakan terminologi AspectJ) konstruktor kelas dan menyuntikkan properti yang relevan, mungkin diberikan atribut khusus.
sumber
Saya tidak begitu setuju. Setidaknya tidak secara umum.
Pabrik sederhana:
Injeksi sederhana:
Kedua cuplikan memiliki tujuan yang sama, mereka mengatur tautan antara
IFoo
danFoo
. Yang lainnya hanyalah sintaks.Mengubah
Foo
menjadiADifferentFoo
membutuhkan banyak upaya tepat di kedua sampel kode.Saya pernah mendengar orang berpendapat bahwa injeksi ketergantungan memungkinkan ikatan yang berbeda untuk digunakan, tetapi argumen yang sama dapat dibuat tentang pembuatan pabrik yang berbeda. Memilih ikatan yang tepat sama rumitnya dengan memilih pabrik yang tepat.
Metode pabrik memungkinkan Anda untuk menggunakan misalnya
Foo
di beberapa tempat danADifferentFoo
di tempat lain. Beberapa mungkin menyebut ini baik (berguna jika Anda membutuhkannya), beberapa mungkin menyebut ini buruk (Anda bisa melakukan pekerjaan setengah-setengah untuk mengganti semuanya).Namun, tidak terlalu sulit untuk menghindari ambiguitas ini jika Anda tetap menggunakan metode tunggal yang mengembalikan
IFoo
sehingga Anda selalu memiliki sumber tunggal. Jika Anda tidak ingin menembak diri sendiri di kaki, jangan memegang senapan yang dimuat, atau pastikan untuk tidak mengarahkannya ke kaki Anda.Inilah sebabnya mengapa beberapa orang lebih suka untuk secara eksplisit mengambil dependensi dalam konstruktor, seperti ini:
Saya pernah mendengar argumen pro (tanpa konstruktor mengasapi), saya pernah mendengar argumen con (menggunakan konstruktor memungkinkan lebih banyak otomatisasi DI).
Secara pribadi, sementara saya telah menghasilkan untuk senior kami yang ingin menggunakan argumen konstruktor, saya telah melihat masalah dengan dropdownlist di VS (kanan atas, untuk menelusuri metode kelas saat ini) di mana nama metode tidak terlihat ketika satu metode tanda tangan lebih panjang dari layar saya (=> konstruktor kembung).
Pada tingkat teknis, saya juga tidak peduli. Pilihan mana pun membutuhkan usaha yang sama untuk mengetik. Dan karena Anda menggunakan DI, Anda biasanya tidak akan memanggil konstruktor secara manual. Tetapi bug Visual Studio UI membuat saya mendukung tidak membengkak argumen konstruktor.
Sebagai catatan tambahan, injeksi ketergantungan dan pabrik tidak saling eksklusif . Saya memiliki kasus-kasus di mana daripada memasukkan dependensi, saya telah memasukkan sebuah pabrik yang menghasilkan dependensi (NInject untungnya memungkinkan Anda untuk menggunakan
Func<IFoo>
sehingga Anda tidak perlu membuat kelas pabrik yang sebenarnya).Kasus penggunaan untuk ini jarang terjadi tetapi memang ada.
sumber
Dalam contoh tiruan ini, kelas pabrik digunakan saat runtime untuk menentukan jenis objek permintaan HTTP yang masuk untuk instantiate, berdasarkan metode permintaan HTTP. Pabrik itu sendiri disuntikkan dengan wadah injeksi ketergantungan. Ini memungkinkan pabrik untuk membuat penentuan runtime dan membiarkan wadah injeksi ketergantungan menangani dependensi. Setiap objek permintaan HTTP inbound memiliki setidaknya empat dependensi (superglobals dan objek lainnya).
Kode klien untuk pengaturan tipe MVC, dalam terpusat
index.php
, mungkin terlihat seperti berikut (validasi dihilangkan).Atau, Anda dapat menghapus sifat statis (dan kata kunci) dari pabrik dan memungkinkan injector ketergantungan untuk mengelola semuanya (karenanya, konstruktor yang dikomentari). Namun, Anda harus mengubah beberapa (bukan konstanta) dari referensi anggota kelas (
self
) menjadi anggota contoh ($this
).sumber