MVVM dan pola layanan

14

Saya sedang membangun aplikasi WPF menggunakan pola MVVM. Saat ini, model tampilan saya memanggil lapisan layanan untuk mengambil model (bagaimana tidak relevan dengan model tampilan) dan mengubahnya menjadi model tampilan. Saya menggunakan injeksi konstruktor untuk melewati layanan yang diperlukan untuk viewmodel.

Ini mudah diuji dan berfungsi dengan baik untuk model tampilan dengan sedikit dependensi, tetapi segera setelah saya mencoba membuat model viewModels untuk model yang kompleks, saya memiliki konstruktor dengan BANYAK layanan yang disuntikkan di dalamnya (satu untuk mengambil setiap dependensi dan daftar semua nilai yang tersedia) untuk mengikat ke itemSumber misalnya). Saya ingin tahu bagaimana menangani beberapa layanan seperti itu dan masih memiliki model tampilan yang dapat saya uji dengan mudah.

Saya sedang memikirkan beberapa solusi:

  1. Membuat layanan singleton (IServices) yang berisi semua layanan yang tersedia sebagai antarmuka. Contoh: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). Dengan begitu, saya tidak memiliki konstruktor besar dengan banyak parameter layanan di dalamnya.

  2. Membuat fasad untuk layanan yang digunakan oleh viewModel dan meneruskan objek ini di ctor viewmodel saya. Tapi kemudian, saya harus membuat fasad untuk masing-masing model viewe kompleks saya, dan mungkin agak banyak ...

Menurut Anda apa cara "benar" untuk mengimplementasikan arsitektur semacam ini?

alfa-alfa
sumber
Saya pikir cara yang "tepat" untuk melakukannya adalah membuat layer terpisah yang memanggil layanan dan melakukan casting apa pun yang diperlukan untuk membuat ViewModel. ViewModels Anda seharusnya tidak bertanggung jawab untuk membuat sendiri.
Amy Blankenship
@AmyBlankenship: Lihat Model tidak harus (atau bahkan harus dapat) membuat sendiri, tetapi kadang-kadang akan bertanggung jawab untuk membuat model tampilan lain . Wadah IoC dengan dukungan pabrik otomatis adalah bantuan besar di sini.
Aaronaught
"Will terkadang" dan "harus" adalah dua binatang yang berbeda;)
Amy Blankenship
@AmyBlankenship: Apakah Anda menyarankan agar model tampilan tidak harus membuat model tampilan lain? Itu pil yang sulit untuk ditelan. Saya bisa mengerti mengatakan bahwa model tampilan tidak boleh digunakan newuntuk membuat model tampilan lain, tetapi pikirkan sesuatu yang sederhana seperti aplikasi MDI di mana mengklik tombol atau menu "dokumen baru" akan menambah tab baru atau membuka jendela baru. Shell / konduktor harus dapat membuat contoh baru dari sesuatu , bahkan jika itu tersembunyi di balik satu atau beberapa lapisan tipuan.
Aaronaught
Yah, tentu itu harus memiliki kemampuan untuk meminta agar suatu Tampilan dibuat di suatu tempat. Tetapi untuk membuatnya sendiri? Tidak di duniaku :). Tetapi sekali lagi, di dunia tempat saya tinggal, kami menyebut VM "Model Presentasi."
Amy Blankenship

Jawaban:

22

Faktanya, kedua solusi ini buruk.

Membuat layanan singleton (IServices) yang berisi semua layanan yang tersedia sebagai antarmuka. Contoh: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). Dengan begitu, saya tidak memiliki konstruktor besar dengan banyak parameter layanan di dalamnya.

Ini pada dasarnya adalah Pola Penentu Lokasi Layanan , yang merupakan anti-pola. Jika Anda melakukan ini, Anda tidak akan lagi dapat memahami apa yang sebenarnya tergantung pada model tampilan tanpa melihat implementasi privatnya, yang akan membuatnya sangat sulit untuk diuji atau diperbaiki.

Membuat fasad untuk layanan yang digunakan oleh viewModel dan meneruskan objek ini di ctor viewmodel saya. Tapi kemudian, saya harus membuat fasad untuk masing-masing model viewe kompleks saya, dan mungkin agak banyak ...

Ini bukan anti-pola tetapi ini adalah bau kode. Pada dasarnya Anda membuat objek parameter , tetapi titik dari pola PO refactoring adalah untuk berurusan dengan set parameter yang sering digunakan dan di banyak tempat yang berbeda , sedangkan parameter ini hanya akan pernah digunakan sekali. Seperti yang Anda sebutkan, itu akan membuat banyak kode mengasapi tanpa manfaat nyata, dan tidak akan bermain bagus dengan banyak wadah IOC.

Faktanya, kedua strategi di atas mengabaikan masalah keseluruhan, yaitu bahwa pemasangan terlalu tinggi antara model tampilan dan layanan . Cukup menyembunyikan dependensi ini di pelacak layanan atau objek parameter tidak benar-benar mengubah berapa banyak objek lain yang bergantung pada model tampilan.

Pikirkan bagaimana Anda akan menguji unit dari salah satu model tampilan ini. Seberapa besar kode pengaturan Anda? Berapa banyak hal yang perlu diinisialisasi agar dapat berfungsi?

Banyak orang yang memulai dengan MVVM mencoba membuat model tampilan untuk seluruh layar , yang pada dasarnya merupakan pendekatan yang salah. MVVM adalah semua tentang komposisi , dan layar dengan banyak fungsi harus terdiri dari beberapa model tampilan yang berbeda, masing-masing tergantung pada hanya satu atau beberapa model / layanan internal. Jika mereka perlu berkomunikasi satu sama lain, Anda melakukannya melalui pub / sub (perantara pesan, bus acara, dll.)

Apa yang sebenarnya perlu Anda lakukan adalah memperbaiki model tampilan Anda sehingga mereka memiliki lebih sedikit dependensi . Kemudian, jika Anda perlu memiliki "layar" agregat, Anda membuat model tampilan lain untuk mengagregasi model tampilan yang lebih kecil. Model tampilan agregat ini tidak harus melakukan banyak hal dengan sendirinya, sehingga pada gilirannya juga cukup mudah untuk dipahami dan diuji.

Jika Anda telah melakukan ini dengan benar, itu harus jelas hanya dari melihat kode, karena Anda akan memiliki model tampilan pendek, ringkas, spesifik, dan dapat diuji.

Aaronaught
sumber
Ya, itulah yang mungkin akhirnya akan saya lakukan! Terima kasih banyak, tuan.
alfa-alfa
Yah, saya sudah berasumsi dia sudah mencoba ini, tetapi tidak berhasil. @ alfa-alfa
Euphoric
@ Euphoric: Bagaimana Anda "tidak berhasil" dalam hal ini? Seperti yang dikatakan Yoda: Lakukan atau tidak, tidak ada upaya.
Aaronaught
@Aonaonaught Misalnya dia benar-benar membutuhkan semua data dalam model tampilan tunggal. Mungkin dia memiliki kisi dan kolom yang berbeda berasal dari layanan yang berbeda. Anda tidak dapat melakukannya dengan komposisi.
Euforia
@ Euphoric: Sebenarnya, Anda bisa menyelesaikannya dengan komposisi, tapi itu bisa dilakukan di bawah level model tampilan. Ini hanyalah masalah menciptakan abstraksi yang tepat. Dalam hal ini, Anda hanya perlu satu layanan untuk menangani permintaan awal untuk mendapatkan daftar ID, dan urutan / daftar / array "pengaya" yang memberi keterangan dengan informasi mereka sendiri. Buat grid itu sendiri model tampilan sendiri, dan Anda telah memecahkan masalah dengan dua dependensi secara efektif, dan sangat mudah untuk menguji.
Aaronaught
1

Saya bisa menulis buku tentang ini ... sebenarnya saya;)

Pertama, tidak ada cara "benar" yang universal untuk melakukan sesuatu. Anda harus mempertimbangkan faktor-faktor lain.

Mungkin saja layanan Anda berbutir terlalu halus. Membungkus Layanan dengan Fasad yang menyediakan antarmuka yang menggunakan Viewmodel tertentu atau bahkan sekelompok ViewModels terkait mungkin merupakan solusi yang lebih baik.

Simpler masih akan membungkus layanan menjadi satu fasad yang digunakan semua model viewm. Tentu saja itu berpotensi menjadi antarmuka yang sangat besar dengan banyak fungsi yang tidak perlu untuk skenario rata-rata. Tapi saya akan mengatakan itu tidak berbeda dengan router pesan yang menangani setiap pesan di sistem Anda.

Bahkan, apa yang saya lihat banyak arsitektur akhirnya berkembang menjadi pesan bus dibangun di sekitar sesuatu seperti pola Agregator Acara. Tes mudah dilakukan di sana karena kelas pengujian Anda hanya mendaftarkan pendengar dengan EA dan menjalankan respons yang sesuai. Tetapi itu adalah skenario lanjutan yang membutuhkan waktu untuk berkembang. Saya katakan mulai dengan fasad pemersatu dan pergi dari sana.

Michael Brown
sumber
Fasad layanan besar-besaran sangat berbeda dari pialang pesan. Itu hampir di ujung spektrum ketergantungan. Ciri khas arsitektur ini adalah bahwa pengirim tidak tahu apa-apa tentang penerima, dan mungkin ada banyak penerima (pub / sub atau multicast). Mungkin Anda mengacaukannya dengan "remoting" gaya RPC yang hanya mengekspos layanan tradisional melalui protokol jarak jauh, dan pengirim masih digabungkan ke penerima, baik secara fisik (alamat endpoint) dan secara logis (nilai pengembalian).
Aaronaught
Kesamaannya adalah bahwa fasad bertindak seperti router, penelepon tidak tahu layanan / layanan mana yang menangani panggilan seperti halnya klien yang mengirim pesan tidak tahu siapa yang menangani pesan.
Michael Brown
Ya, tapi façade kemudian menjadi Obyek Dewa . Ini memiliki semua dependensi yang dimiliki Model Tampilan, mungkin lebih karena itu akan dibagikan oleh beberapa. Akibatnya, Anda telah menghilangkan manfaat dari kopling longgar yang awalnya Anda kerjakan dengan keras, karena sekarang, setiap kali sesuatu menyentuh mega-façade, Anda tidak memiliki petunjuk apa fungsionalitasnya. Gambar menulis tes unit untuk kelas yang menggunakan fasad. Anda membuat tiruan untuk façade. Sekarang, metode apa yang Anda tiru? Seperti apa kode pengaturan Anda?
Aaronaught
Ini sangat berbeda dari pialang pesan karena pialang juga tidak tahu apa-apa tentang implementasi penangan pesan . Menggunakan IoC di bawah tenda. Façade tahu segalanya tentang penerima karena harus meneruskan panggilan kepada mereka. Bus tidak memiliki kopling; façade memiliki kopling eferen yang sangat tinggi. Hampir semua yang Anda ubah, di mana saja, akan memengaruhi façade juga.
Aaronaught
Saya pikir bagian dari kebingungan di sini - dan saya melihat ini cukup banyak - adalah arti ketergantungan . Jika Anda memiliki kelas yang bergantung pada satu kelas lain, tetapi memanggil 4 metode kelas itu, ia memiliki 4 dependensi, bukan 1. Menempatkan semuanya di belakang façade tidak mengubah jumlah dependensi, itu hanya membuat mereka lebih sulit untuk dipahami .
Aaronaught
0

Mengapa tidak menggabungkan keduanya?

Buat fasad dan letakkan semua layanan yang digunakan oleh viewmodels Anda. Maka Anda dapat memiliki fasad tunggal untuk semua model Anda tanpa kata S buruk.

Atau Anda bisa menggunakan injeksi properti alih-alih injeksi konstruktor. Tapi kemudian, Anda perlu memastikan bahwa mereka disuntik dengan benar.

Euforia
sumber
Ini akan menjadi jawaban yang lebih baik jika Anda memberikan contoh di pseudo-C #.
Robert Harvey