Cara membuat membuat viewmodels saat runtime tidak terlalu menyakitkan

17

Saya minta maaf untuk pertanyaan yang panjang, ini sedikit berbunyi kata-kata kasar, tapi saya berjanji tidak! Saya telah merangkum pertanyaan saya di bawah

Di dunia MVC, banyak hal langsung. Model memiliki status, View menunjukkan Model, dan Controller melakukan hal - hal ke / dengan Model (pada dasarnya), controller tidak memiliki status. Untuk melakukan hal-hal Controller memiliki beberapa dependensi pada layanan web, repositori, lot. Ketika Anda instantiate controller Anda peduli tentang penyediaan dependensi itu, tidak ada yang lain. Ketika Anda menjalankan suatu tindakan (metode pada Kontroler), Anda menggunakan dependensi itu untuk mengambil atau memperbarui Model atau memanggil beberapa layanan domain lainnya. Jika ada konteks apa pun, katakanlah seperti beberapa pengguna ingin melihat detail item tertentu, Anda meneruskan ID item itu sebagai parameter ke Action. Tidak ada tempat di Controller ada referensi ke negara. Sejauh ini baik.

Masukkan MVVM. Saya suka WPF, saya suka pengikatan data. Saya suka kerangka kerja yang membuat pengikatan data ke ViewModels lebih mudah (menggunakan Caliburn Micro atm). Saya merasa hal-hal yang kurang langsung di dunia ini. Mari kita lakukan latihan lagi: Model memiliki negara, View menunjukkan ViewModel, dan ViewModel melakukan hal-hal untuk / dengan Model (pada dasarnya), ViewModel tidak memiliki negara! (untuk mengklarifikasi; mungkin itu mendelegasikan semua properti ke satu atau lebih Model, tetapi itu berarti ia harus memiliki referensi ke model dengan satu cara atau yang lain, yang merupakan keadaan itu sendiri) Untuk melakukanbarang yang ViewModel memiliki beberapa ketergantungan pada layanan web, repositori, banyak. Ketika Anda instantiate ViewModel Anda peduli tentang penyediaan dependensi tersebut, tetapi juga negara. Dan ini, tuan dan nyonya, mengganggu saya tanpa akhir.

Setiap kali Anda perlu instantiate ProductDetailsViewModeldari ProductSearchViewModel(dari mana Anda memanggil ProductSearchWebServiceyang pada gilirannya kembali IEnumerable<ProductDTO>, semua orang masih bersama saya?), Anda dapat melakukan salah satu dari ini:

  • panggilan new ProductDetailsViewModel(productDTO, _shoppingCartWebService /* dependcy */);, ini buruk, bayangkan 3 dependensi lebih, ini berarti ProductSearchViewModelkebutuhan untuk mengambil dependensi itu juga. Mengubah konstruktor juga menyakitkan.
  • sebut _myInjectedProductDetailsViewModelFactory.Create().Initialize(productDTO);, pabrik hanyalah sebuah Func, mereka mudah dihasilkan oleh sebagian besar kerangka kerja IoC. Saya pikir ini buruk karena metode Init adalah abstraksi yang bocor. Anda juga tidak dapat menggunakan kata kunci hanya baca untuk bidang yang diatur dalam metode Init. Saya yakin ada beberapa alasan lagi.
  • sebut _myInjectedProductDetailsViewModelAbstractFactory.Create(productDTO);So ... ini adalah pola (pabrik abstrak) yang biasanya direkomendasikan untuk masalah jenis ini. Saya pikir itu jenius karena memuaskan keinginan saya untuk mengetik statis, sampai saya benar-benar mulai menggunakannya. Jumlah kode boilerplate saya pikir terlalu banyak (Anda tahu, terlepas dari nama variabel konyol yang saya gunakan). Untuk setiap ViewModel yang membutuhkan parameter runtime Anda akan mendapatkan dua file tambahan (antarmuka pabrik dan implementasi), dan Anda perlu mengetikkan dependensi non-runtime seperti 4 kali tambahan. Dan setiap kali dependensi berubah, Anda juga bisa mengubahnya di pabrik. Rasanya seperti saya bahkan tidak menggunakan wadah DI lagi. (Saya pikir Castle Windsor memiliki beberapa solusi untuk ini [dengan kekurangannya sendiri, koreksi saya jika saya salah]).
  • lakukan sesuatu dengan jenis atau kamus anonim. Saya suka mengetik statis.

Jadi ya. Mencampuradukkan keadaan dan perilaku dengan cara ini menciptakan masalah yang tidak ada sama sekali di MVC. Dan saya merasa saat ini tidak ada solusi yang cukup memadai untuk masalah ini. Sekarang saya ingin mengamati beberapa hal:

  • Orang-orang benar-benar menggunakan MVVM. Jadi mereka tidak peduli dengan semua hal di atas, atau mereka memiliki solusi cemerlang lainnya.
  • Saya belum menemukan contoh mendalam MVVM dengan WPF. Sebagai contoh, proyek sampel NDDD sangat membantu saya memahami beberapa konsep DDD. Saya sangat suka jika seseorang bisa mengarahkan saya ke arah yang mirip dengan MVVM / WPF.
  • Mungkin saya melakukan kesalahan MVVM dan saya harus membalikkan desain saya. Mungkin saya seharusnya tidak memiliki masalah ini sama sekali. Yah saya tahu orang lain telah mengajukan pertanyaan yang sama jadi saya pikir saya bukan satu-satunya.

Untuk meringkas

  • Apakah saya benar menyimpulkan bahwa memiliki ViewModel menjadi titik integrasi untuk kedua negara dan perilaku adalah alasan untuk beberapa kesulitan dengan pola MVVM secara keseluruhan?
  • Apakah menggunakan pola abstrak pabrik satu-satunya / cara terbaik untuk instantiate ViewModel dengan cara yang diketik secara statis?
  • Apakah ada sesuatu seperti implementasi referensi mendalam yang tersedia?
  • Apakah memiliki banyak ViewModels dengan kedua status / perilaku bau desain?
dvdvorle
sumber
10
Ini terlalu lama untuk dibaca, pertimbangkan merevisi, ada banyak hal yang tidak relevan di sana. Anda mungkin kehilangan jawaban yang baik karena orang tidak akan repot-repot membaca semua itu.
yannis
Anda bilang Anda suka Caliburn.Micro, namun Anda tidak tahu bagaimana kerangka kerja ini dapat membantu instantiate model tampilan baru? Lihat beberapa contohnya.
Euforia
@ Euphoric Bisakah Anda sedikit lebih spesifik, Google sepertinya tidak membantu saya di sini. Punya beberapa kata kunci yang bisa saya cari?
dvdvorle
3
Saya pikir Anda menyederhanakan MVC sedikit. Tentu Tampilan menunjukkan Model di awal, tetapi selama operasi itu berubah keadaan. Menurut saya, keadaan yang berubah ini adalah "Edit Model". Yaitu, versi model yang rata dengan pembatasan konsistensi yang berkurang. Sebenarnya, apa yang saya sebut Model Edit adalah MVVM ViewModel. Itu memegang status saat dalam transisi, yang sebelumnya dipegang oleh View in MVC, atau didorong kembali ke versi Model yang tidak dikomit, di mana saya tidak berpikir itu milik. Jadi Anda sudah menyatakan "fluks" sebelumnya. Sekarang semuanya ada di ViewModel.
Scott Whitlock
@ScottWhitlock Saya memang menyederhanakan MVC. Tapi saya tidak mengatakan itu salah bahwa keadaan "dalam fluks" ada di dalam ViewModel, saya mengatakan bahwa menjejalkan perilaku di sana juga membuat lebih sulit untuk menginisialisasi ViewModel ke keadaan yang dapat digunakan dari mengatakan, ViewModel lain. "Edit Model" Anda di MVC tidak tahu bagaimana cara Simpan sendiri (tidak memiliki metode Simpan). Tetapi controller mengetahui hal ini, dan memiliki semua dependensi yang diperlukan untuk melakukan itu.
dvdvorle

Jawaban:

2

Masalah dependensi saat memulai model tampilan baru dapat ditangani dengan IOC.

public class MyCustomViewModel{
  private readonly IShoppingCartWebService _cartService;

  private readonly ITimeService _timeService;

  public ProductDTO ProductDTO { get; set; }

  public ProductDetailsViewModel(IShoppingCartWebService cartService, ITimeService timeService){
    _cartService = cartService;
    _timeService = timeService;
  }
}

Saat menyiapkan wadah ...

Container.Register<IShoppingCartWebService,ShoppingCartWebSerivce>().As.Singleton();
Container.Register<ITimeService,TimeService>().As.Singleton();
Container.Register<ProductDetailsViewModel>();

Saat Anda membutuhkan model tampilan Anda:

var viewmodel = Container.Resolve<ProductDetailsViewModel>();
viewmodel.ProductDTO = myProductDTO;

Ketika menggunakan kerangka kerja seperti caliburn micro, seringkali sudah ada beberapa bentuk wadah IOC.

SomeCompositionView view = new SomeCompositionView();
ISomeCompositionViewModel viewModel = IoC.Get<ISomeCompositionViewModel>();
ViewModelBinder.Bind(viewModel, view, null);
Mike
sumber
1

Saya bekerja setiap hari dengan ASP.NET MVC dan telah bekerja di WPF selama lebih dari setahun dan ini adalah bagaimana saya melihatnya:

MVC

Kontroler seharusnya mengatur aksi (ambil ini, tambahkan itu).

Tampilan bertanggung jawab untuk menampilkan model.

Model ini biasanya mencakup data (mis. UserId, FirstName) dan juga status (mis. Judul) dan biasanya khusus tampilan.

MVVM

Model biasanya hanya menyimpan data (mis. UserId, FirstName) dan biasanya diedarkan

Model tampilan mencakup perilaku tampilan (metode), datanya (model) dan interaksi (perintah) - mirip dengan pola MVP aktif di mana presenter mengetahui model tersebut. Model tampilan adalah tampilan spesifik (1 tampilan = 1 model tampilan).

Tampilan bertanggung jawab untuk menampilkan data dan pengikatan data ke model tampilan. Ketika sebuah view dibuat, biasanya model view yang terkait dibuat dengannya.


Yang harus Anda ingat adalah bahwa pola presentasi MVVM khusus untuk WPF / Silverlight karena sifat pengikatan datanya.

Tampilan biasanya tahu model tampilan mana yang terkait (atau abstraksi).

Saya akan menyarankan Anda memperlakukan model tampilan sebagai singleton, meskipun itu instantiated per view. Dengan kata lain, Anda harus dapat membuatnya melalui DI melalui wadah IOC dan memanggil metode yang tepat untuk mengatakannya; memuat modelnya berdasarkan parameter. Sesuatu seperti ini:

public partial class EditUserView
{
    public EditUserView(IContainer container, int userId) : this() {
        var viewModel = container.Resolve<EditUserViewModel>();
        viewModel.LoadModel(userId);
        DataContext = viewModel;
    }
}

Sebagai contoh dalam kasus ini, Anda tidak akan membuat model tampilan khusus untuk pengguna yang diperbarui - sebaliknya model akan berisi data spesifik pengguna yang akan dimuat melalui beberapa panggilan pada model tampilan.

Shelakel
sumber
Jika FirstName saya adalah "Peter" dan Judul saya adalah {"Rev", "Dr"} *, mengapa Anda mempertimbangkan data FirstName dan status Judul? Atau bisakah Anda menjelaskan contoh Anda? * tidak juga
Pete Kirkham
@PeteKirkham - contoh 'judul' yang saya maksudkan dalam konteks say a combobox. Umumnya ketika Anda mengirim informasi untuk bertahan Anda tidak akan mengirim negara (mis. Daftar negara bagian / provinsi / judul) yang digunakan untuk membuat pilihan dari. Status apa pun yang berharga untuk ditransfer dengan data (mis. Adalah nama pengguna yang digunakan) harus diperiksa pada titik pemrosesan karena statusnya mungkin sudah basi (jika Anda menggunakan beberapa pola asinkron seperti antrian pesan).
Shelakel
Meskipun sudah dua tahun sejak posting ini, saya harus membuat komentar untuk pemirsa di masa depan: Dua hal mengganggu saya dengan jawaban Anda. Tampilan mungkin terkait dengan satu ViewModel, tetapi ViewModel dapat diwakili oleh beberapa Tampilan. Kedua, apa yang Anda gambarkan adalah anti-pola Layanan Locator. IMHO Anda tidak harus secara langsung menyelesaikan viewmodels di mana-mana. Untuk itulah DI. Buat keputusan Anda pada poin lebih sedikit yang Anda bisa. Biarkan Caliburn melakukan pekerjaan ini untuk Anda, misalnya.
Jony Adamit
1

Jawaban singkat untuk pertanyaan Anda:

  1. Ya Status + Perilaku mengarah ke masalah-masalah itu, tetapi ini berlaku untuk semua OO. Penyebab sesungguhnya adalah penggabungan ViewModels yang merupakan jenis pelanggaran SRP.
  2. Mungkin diketik secara statis. Tetapi Anda harus mengurangi / menghilangkan kebutuhan Anda akan instantiating ViewModels dari ViewModels lainnya.
  3. Bukannya aku sadar.
  4. Tidak, tetapi memiliki ViewModels dengan status & perilaku yang tidak terkait (Seperti beberapa referensi Model dan beberapa referensi ViewModel)

Versi panjang:

Kami menghadapi Masalah yang sama, dan menemukan beberapa hal yang dapat membantu Anda. Meskipun saya tidak tahu solusi "ajaib", hal-hal itu sedikit meredakan rasa sakit.

  1. Terapkan model yang dapat diikat dari DTO untuk pelacakan perubahan dan validasi. "Data" -ViewModels itu tidak boleh bergantung pada layanan dan tidak berasal dari wadah. Mereka bisa saja "baru" ditingkatkan, diedarkan dan bahkan dapat diturunkan dari DTO. Bottomline adalah untuk mengimplementasikan Model khusus untuk aplikasi Anda (Seperti MVC).

  2. Decouple ViewModels Anda. Caliburn membuatnya mudah untuk memasangkan ViewModels bersama. Ia bahkan menyarankannya melalui model Layar / Konduktornya. Tetapi penggabungan ini membuat ViewModels sulit untuk diuji unit, membuat banyak dependensi dan yang paling penting: Membebankan beban mengelola siklus hidup ViewModel pada ViewModels Anda. Salah satu cara untuk memisahkan mereka adalah menggunakan sesuatu seperti layanan navigasi atau pengontrol ViewModel. Misalnya

    antarmuka publik IShowViewModels {void Show (objek inlineArgumentsAsAnonymousType, string regionId); }

Bahkan lebih baik melakukan ini dengan beberapa bentuk pesan. Tetapi yang penting adalah tidak menangani siklus hidup ViewModel dari ViewModels lainnya. Dalam MVC Controllers tidak saling bergantung, dan dalam MVVM ViewModels tidak boleh saling bergantung. Integrasikan mereka melalui beberapa cara lain.

  1. Gunakan wadah Anda "ketat" -typed / fitur dinamis. Meskipun mungkin bisa membuat sesuatu seperti INeedData<T1,T2,...>dan menegakkan parameter penciptaan aman-jenis, itu tidak layak. Juga membuat pabrik untuk setiap jenis ViewModel tidak sepadan. Sebagian besar Kontainer IoC memberikan solusi untuk ini. Anda akan mendapatkan kesalahan saat runtime tetapi de-coupling dan unit testability sepadan. Anda masih melakukan semacam tes integrasi dan kesalahan-kesalahan itu terlihat dengan mudah.
sanosdole
sumber
0

Cara saya biasanya melakukan ini (menggunakan PRISM), adalah setiap perakitan berisi modul inisialisasi wadah, di mana semua antarmuka, contoh terdaftar pada startup.

private void RegisterResources()
{
    Container.RegisterType<IDataService, DataService>();
    Container.RegisterType<IProductSearchViewModel, ProductSearchViewModel>();
    Container.RegisterType<IProductDetailsViewModel, ProductDetailsViewModel>();
}

Dan dengan contoh kelas Anda, akan dilaksanakan seperti ini, dengan wadah yang dilaluinya. Dengan cara ini setiap dependensi baru dapat ditambahkan dengan mudah karena Anda sudah memiliki akses ke wadah.

/// <summary>
/// IDataService Interface
/// </summary>
public interface IDataService
{
    DataTable GetSomeData();
}

public class DataService : IDataService
{
    public DataTable GetSomeData()
    {
        MessageBox.Show("This is a call to the GetSomeData() method.");

        var someData = new DataTable("SomeData");
        return someData;
    }
}

public interface IProductSearchViewModel
{
}

public class ProductSearchViewModel : IProductSearchViewModel
{
    private readonly IUnityContainer _container;

    /// <summary>
    /// This will get resolved if it's been added to the container.
    /// Or alternately you could use constructor resolution. 
    /// </summary>
    [Dependency]
    public IDataService DataService { get; set; }

    public ProductSearchViewModel(IUnityContainer container)
    {
        _container = container;
    }

    public void SearchAndDisplay()
    {
        DataTable results = DataService.GetSomeData();

        var detailsViewModel = _container.Resolve<IProductDetailsViewModel>();
        detailsViewModel.DisplaySomeDataInView(results);

        // Create the view, usually resolve using region manager etc.
        var detailsView = new DetailsView() { DataContext = detailsViewModel };
    }
}

public interface IProductDetailsViewModel
{
    void DisplaySomeDataInView(DataTable dataTable);
}

public class ProductDetailsViewModel : IProductDetailsViewModel
{
    private readonly IUnityContainer _container;

    public ProductDetailsViewModel(IUnityContainer container)
    {
        _container = container;
    }

    public void DisplaySomeDataInView(DataTable dataTable)
    {
    }
}

Sangat umum untuk memiliki kelas ViewModelBase, dari mana semua model tampilan Anda berasal, yang berisi referensi ke wadah. Selama Anda terbiasa menyelesaikan semua model tampilan alih-alih new()'ingmereka, itu harus membuat semua resolusi ketergantungan jauh lebih sederhana.

Martin Cooper
sumber
0

Terkadang lebih baik untuk pergi ke definisi paling sederhana daripada contoh lengkap: http://en.wikipedia.org/wiki/Model_View_ViewModel mungkin membaca contoh ZK Java lebih mencerahkan daripada C # satu.

Lain kali dengarkan insting Anda ...

Apakah memiliki banyak ViewModels dengan kedua status / perilaku bau desain?

Apakah model Anda objek per pemetaan tabel? Mungkin ORM akan membantu pemetaan ke objek domain saat menangani bisnis atau memperbarui beberapa tabel.

Gerry King
sumber