Saya mencoba untuk mendapatkan Unity untuk mengelola pembuatan objek saya dan saya ingin memiliki beberapa parameter inisialisasi yang tidak diketahui hingga run-time:
Saat ini satu-satunya cara saya bisa memikirkan cara untuk melakukannya adalah dengan memiliki metode Init pada antarmuka.
interface IMyIntf {
void Initialize(string runTimeParam);
string RunTimeParam { get; }
}
Kemudian untuk menggunakannya (dalam Unity) saya akan melakukan ini:
var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");
Dalam skenario ini runTimeParam
param ditentukan pada saat run-time berdasarkan input pengguna. Kasus sepele di sini hanya mengembalikan nilai runTimeParam
tetapi dalam kenyataannya parameter akan menjadi sesuatu seperti nama file dan metode inisialisasi akan melakukan sesuatu dengan file tersebut.
Ini menciptakan sejumlah masalah, yaitu bahwa Initialize
metode ini tersedia di antarmuka dan dapat dipanggil beberapa kali. Mengatur bendera dalam implementasi dan melempar pengecualian pada panggilan berulang untuk Initialize
tampaknya cara kikuk.
Pada titik di mana saya menyelesaikan antarmuka saya, saya tidak ingin tahu apa-apa tentang implementasi IMyIntf
. Apa yang saya inginkan, bagaimanapun, adalah pengetahuan bahwa antarmuka ini membutuhkan parameter inisialisasi satu kali tertentu. Apakah ada cara entah bagaimana menjelaskan (atribut?) Antarmuka dengan informasi ini dan meneruskannya ke kerangka kerja ketika objek dibuat?
Sunting: Menjelaskan antarmuka sedikit lebih.
sumber
runTimeParam
adalah ketergantungan yang ditentukan pada saat dijalankan berdasarkan input pengguna. Haruskah alternatif untuk ini membaginya menjadi dua antarmuka - satu untuk inisialisasi dan satu lagi untuk menyimpan nilai?Jawaban:
Setiap tempat di mana Anda memerlukan nilai run-time untuk membangun ketergantungan tertentu, Abstract Factory adalah solusinya.
Memiliki metode Inisialisasi pada antarmuka bau Abstraksi Leaky .
Dalam kasus Anda, saya akan mengatakan bahwa Anda harus memodelkan
IMyIntf
antarmuka tentang bagaimana Anda perlu menggunakannya - bukan bagaimana Anda bermaksud untuk membuat implementasi darinya. Itu detail implementasi.Dengan demikian, antarmuka seharusnya:
Sekarang tentukan Pabrik Abstrak:
Anda sekarang dapat membuat implementasi konkret
IMyIntfFactory
yang menciptakan contoh konkretIMyIntf
seperti ini:Perhatikan bagaimana ini memungkinkan kami untuk melindungi invarian kelas dengan menggunakan
readonly
kata kunci. Tidak diperlukan metode inisialisasi bau.Sebuah
IMyIntfFactory
implementasi mungkin sesederhana ini:Di semua konsumen Anda di mana Anda membutuhkan sebuah
IMyIntf
instance, Anda cukup mengandalkanIMyIntfFactory
dengan memintanya melalui Constructor Injection .Setiap Kontainer DI yang bernilai garamnya akan dapat secara otomatis mengirimkan
IMyIntfFactory
contoh kepada Anda jika Anda mendaftarkannya dengan benar.sumber
MyIntf
implementasi pabrik membutuhkan lebih darirunTimeParam
(baca: layanan lain yang ingin diselesaikan oleh IoC), maka Anda masih dihadapkan pada penyelesaian dependensi di pabrik Anda. Saya suka jawaban @ PhilSandler untuk meneruskan dependensi tersebut ke konstruktor pabrik untuk menyelesaikan masalah ini - apakah Anda juga mengambilnya?Biasanya ketika Anda menghadapi situasi ini, Anda perlu mengunjungi kembali desain Anda dan menentukan apakah Anda mencampur objek stateful / data Anda dengan layanan murni Anda. Dalam sebagian besar (tidak semua) kasus, Anda ingin memisahkan kedua jenis objek ini.
Jika Anda memerlukan parameter khusus konteks yang diteruskan dalam konstruktor, salah satu opsi adalah membuat pabrik yang menyelesaikan dependensi layanan Anda melalui konstruktor, dan menggunakan parameter run-time sebagai parameter dari metode Create () (atau Menghasilkan ( ), Build () atau apa pun yang Anda sebutkan metode pabrik Anda).
Memiliki setter atau metode Initialize () umumnya dianggap sebagai desain yang buruk, karena Anda perlu "ingat" untuk memanggil mereka dan pastikan mereka tidak membuka terlalu banyak kondisi implementasi Anda (yaitu apa yang menghentikan seseorang untuk -memanggil inisialisasi atau penyetel?).
sumber
Saya juga telah menemukan situasi ini beberapa kali di lingkungan di mana saya secara dinamis membuat objek ViewModel berdasarkan objek Model (diuraikan dengan sangat baik oleh posting Stackoverflow ini ).
Saya menyukai bagaimana ekstensi Ninject yang memungkinkan Anda membuat pabrik secara dinamis berdasarkan antarmuka:
Bind<IMyFactory>().ToFactory();
Saya tidak dapat menemukan fungsi serupa secara langsung di Unity ; jadi saya menulis ekstensi saya sendiri ke IUnityContainer yang memungkinkan Anda untuk mendaftarkan pabrik yang akan membuat objek baru berdasarkan data dari objek yang ada pada dasarnya memetakan dari satu hierarki jenis ke hierarki jenis yang berbeda: UnityMappingFactory @ GitHub
Dengan tujuan kesederhanaan dan keterbacaan, saya berakhir dengan ekstensi yang memungkinkan Anda untuk secara langsung menentukan pemetaan tanpa mendeklarasikan masing-masing kelas pabrik atau antarmuka (penghemat waktu nyata). Anda cukup menambahkan pemetaan di mana Anda mendaftarkan kelas selama proses bootstrap normal ...
Kemudian Anda cukup mendeklarasikan antarmuka pabrik pemetaan di konstruktor untuk CI dan menggunakan metode Buat () ...
Sebagai bonus tambahan, setiap dependensi tambahan dalam konstruktor dari kelas yang dipetakan juga akan diselesaikan selama pembuatan objek.
Jelas, ini tidak akan menyelesaikan setiap masalah tetapi sejauh ini telah membantu saya, jadi saya pikir saya harus membaginya. Ada lebih banyak dokumentasi di situs proyek di GitHub.
sumber
Saya tidak bisa menjawab dengan terminologi Unity tertentu tetapi sepertinya Anda baru belajar tentang injeksi ketergantungan. Jika demikian, saya mendorong Anda untuk membaca panduan pengguna singkat, jelas, dan informasi untuk Ninject .
Ini akan memandu Anda melalui berbagai opsi yang Anda miliki saat menggunakan DI, dan bagaimana menjelaskan masalah spesifik yang akan Anda hadapi di sepanjang jalan. Dalam kasus Anda, kemungkinan besar Anda ingin menggunakan wadah DI untuk membuat instance objek Anda, dan meminta objek tersebut mendapatkan referensi ke masing-masing dependensinya melalui konstruktor.
Panduan ini juga merinci cara membuat anotasi metode, properti, dan bahkan parameter menggunakan atribut untuk membedakannya saat runtime.
Bahkan jika Anda tidak menggunakan Ninject, panduan akan memberikan Anda konsep dan terminologi fungsi yang sesuai dengan tujuan Anda, dan Anda harus dapat memetakan pengetahuan itu ke Unity atau kerangka kerja DI lainnya (atau meyakinkan Anda untuk mencoba Ninject) .
sumber
Saya pikir saya menyelesaikannya dan rasanya agak sehat, jadi pasti setengah benar :))
Saya terpecah
IMyIntf
menjadi "getter" dan "setter" interface. Begitu:Kemudian implementasinya:
IMyIntfSetter.Initialize()
masih bisa dipanggil beberapa kali tetapi menggunakan bit paradigma Service Locator kita dapat membungkusnya dengan cukup baik sehinggaIMyIntfSetter
hampir merupakan antarmuka internal yang berbedaIMyIntf
.sumber