Model-View-Presenter di WinForms

90

Saya mencoba menerapkan metode MVP untuk pertama kalinya, menggunakan WinForms.

Saya mencoba memahami fungsi setiap lapisan.

Dalam program saya, saya memiliki tombol GUI yang ketika diklik membuka jendela openfiledialog.

Jadi menggunakan MVP, GUI menangani peristiwa klik tombol dan kemudian memanggil presenter.openfile ();

Dalam presenter.openfile (), haruskah itu mendelegasikan pembukaan file itu ke lapisan model, atau karena tidak ada data atau logika untuk diproses, haruskah itu hanya bertindak atas permintaan dan membuka jendela openfiledialog?

Pembaruan: Saya telah memutuskan untuk menawarkan hadiah karena saya merasa saya membutuhkan bantuan lebih lanjut tentang ini, dan lebih disukai disesuaikan dengan poin spesifik saya di bawah ini, sehingga saya memiliki konteks.

Oke, setelah membaca tentang MVP, saya telah memutuskan untuk menerapkan Tampilan Pasif. Secara efektif saya akan memiliki banyak kontrol pada Winform yang akan ditangani oleh Presenter dan kemudian tugas didelegasikan ke Model. Poin spesifik saya ada di bawah ini:

  1. Ketika winform dimuat, itu harus mendapatkan tampilan pohon. Apakah saya benar dalam berpikir bahwa tampilan harus memanggil metode seperti: presenter.gettree (), ini pada gilirannya akan mendelegasikan ke model, yang akan mendapatkan data untuk treeview, membuatnya dan mengkonfigurasinya, mengembalikannya ke presenter, yang pada gilirannya akan meneruskan ke tampilan yang kemudian akan dengan mudah menetapkannya ke, katakanlah, panel?

  2. Apakah ini akan sama untuk semua kontrol data di Winform, karena saya juga memiliki datagridview?

  3. Aplikasi Saya, memiliki sejumlah kelas model dengan perakitan yang sama. Ini juga mendukung arsitektur plugin dengan plugin yang perlu dimuat saat startup. Akankah tampilan hanya memanggil metode presenter, yang pada gilirannya akan memanggil metode yang memuat plugin dan menampilkan informasi dalam tampilan? Tingkatan mana yang kemudian akan mengontrol referensi plugin. Apakah tampilan tersebut menyimpan referensi ke mereka atau presenter?

  4. Apakah saya benar dalam berpikir bahwa tampilan harus menangani setiap hal tentang presentasi, dari warna node tampilan pohon, ke ukuran datagrid, dll?

Saya pikir itu adalah perhatian utama saya dan jika saya mengerti bagaimana alurnya untuk ini, saya pikir saya akan baik-baik saja.

Darren Young
sumber
Tautan ini lostechies.com/derekgreer/2008/11/23/… menjelaskan beberapa gaya MVP. Itu bisa terbukti membantu selain jawaban Johann yang luar biasa.
ak3nat0n

Jawaban:

123

Ini adalah pendapat saya yang sederhana tentang MVP dan masalah spesifik Anda.

Pertama , apa pun yang dapat berinteraksi dengan pengguna, atau hanya ditampilkan, adalah tampilan . Hukum, perilaku, dan karakteristik tampilan seperti itu dijelaskan oleh antarmuka . Antarmuka itu dapat diimplementasikan menggunakan WinForms UI, UI konsol, UI web atau bahkan tanpa UI sama sekali (biasanya saat menguji penyaji) - implementasi konkret tidak masalah selama mematuhi hukum antarmuka tampilan .

Kedua , tampilan selalu dikontrol oleh presenter . Hukum, perilaku dan karakteristik penyaji semacam itu juga dijelaskan oleh antarmuka . Antarmuka tersebut tidak tertarik pada implementasi tampilan konkret selama mematuhi hukum antarmuka tampilan.

Ketiga , karena penyaji mengontrol pandangannya, untuk meminimalkan ketergantungan, sebenarnya tidak ada gunanya memiliki tampilan yang mengetahui apa pun tentang penyaji. Ada kontrak yang disepakati antara penyaji dan tampilan dan itu dinyatakan oleh antarmuka tampilan.

Implikasi dari Ketiga adalah:

  • Presenter tidak memiliki metode apa pun yang dapat dipanggil oleh tampilan, tetapi tampilan tersebut memiliki peristiwa yang dapat dilanggan oleh presenter.
  • Presenter tahu pandangannya. Saya lebih suka melakukannya dengan injeksi konstruktor pada presenter beton.
  • Pandangan tidak tahu apa yang dikendalikan oleh presenter; itu tidak akan pernah diberikan presenter apapun.

Untuk masalah Anda, di atas mungkin terlihat seperti ini dalam kode yang agak disederhanakan:

interface IConfigurationView
{
    event EventHandler SelectConfigurationFile;

    void SetConfigurationFile(string fullPath);
    void Show();
}

class ConfigurationView : IConfigurationView
{
    Form form;
    Button selectConfigurationFileButton;
    Label fullPathLabel;

    public event EventHandler SelectConfigurationFile;

    public ConfigurationView()
    {
        // UI initialization.

        this.selectConfigurationFileButton.Click += delegate
        {
            var Handler = this.SelectConfigurationFile;

            if (Handler != null)
            {
                Handler(this, EventArgs.Empty);
            }
        };
    }

    public void SetConfigurationFile(string fullPath)
    {
        this.fullPathLabel.Text = fullPath;
    }

    public void Show()
    {
        this.form.ShowDialog();        
    }
}

interface IConfigurationPresenter
{
    void ShowView();
}

class ConfigurationPresenter : IConfigurationPresenter
{
    Configuration configuration = new Configuration();
    IConfigurationView view;

    public ConfigurationPresenter(IConfigurationView view)
    {
        this.view = view;            
        this.view.SelectConfigurationFile += delegate
        {
            // The ISelectFilePresenter and ISelectFileView behaviors
            // are implicit here, but in a WinForms case, a call to
            // OpenFileDialog wouldn't be too far fetched...
            var selectFilePresenter = Gimme.The<ISelectFilePresenter>();
            selectFilePresenter.ShowView();
            this.configuration.FullPath = selectFilePresenter.FullPath;
            this.view.SetConfigurationFile(this.configuration.FullPath);
        };
    }

    public void ShowView()
    {
        this.view.SetConfigurationFile(this.configuration.FullPath);
        this.view.Show();
    }
}

Selain di atas, saya biasanya memiliki IViewantarmuka dasar tempat saya menyimpan fileShow() dan setiap tampilan pemilik atau judul tampilan yang biasanya menguntungkan pandangan saya.

Untuk pertanyaan Anda:

1. Saat winform dimuat, ia harus mendapatkan tampilan pohon. Apakah saya benar dalam berpikir bahwa tampilan harus memanggil metode seperti: presenter.gettree (), ini pada gilirannya akan mendelegasikan ke model, yang akan mendapatkan data untuk treeview, membuatnya dan mengkonfigurasinya, mengembalikannya ke presenter, yang pada gilirannya akan meneruskan ke tampilan yang kemudian akan dengan mudah menetapkannya ke, katakanlah, panel?

Saya akan menelepon IConfigurationView.SetTreeData(...)dari IConfigurationPresenter.ShowView(), tepat sebelum panggilan keIConfigurationView.Show()

2. Apakah ini akan sama untuk semua kontrol data di Winform, karena saya juga memiliki datagridview?

Ya, saya akan menelepon IConfigurationView.SetTableData(...)untuk itu. Terserah tampilan untuk memformat data yang diberikan padanya. Presenter hanya mematuhi kontrak tampilan yang diinginkan data tabel.

3. Aplikasi Saya, memiliki sejumlah kelas model dengan perakitan yang sama. Ini juga mendukung arsitektur plugin dengan plugin yang perlu dimuat saat startup. Akankah tampilan hanya memanggil metode presenter, yang pada gilirannya akan memanggil metode yang memuat plugin dan menampilkan informasi dalam tampilan? Tingkatan mana yang kemudian akan mengontrol referensi plugin. Apakah tampilan tersebut menyimpan referensi ke mereka atau presenter?

Jika plugin terkait dengan tampilan, maka tampilan harus tahu tentang plugin tersebut, tetapi bukan penyaji. Jika semuanya tentang data dan model, maka tampilan seharusnya tidak ada hubungannya dengan mereka.

4. Apakah saya benar dalam berpikir bahwa view harus menangani setiap hal tentang presentasi, dari warna node tampilan pohon, ukuran datagrid, dll?

Iya. Anggap saja sebagai penyaji yang menyediakan XML yang mendeskripsikan data dan tampilan yang mengambil data dan menerapkan lembar gaya CSS ke dalamnya. Secara konkret, presenter mungkin memanggil IRoadMapView.SetRoadCondition(RoadCondition.Slippery)dan view kemudian menampilkan jalan dengan warna merah.

Bagaimana dengan data untuk node yang diklik?

5. Jika ketika saya mengklik treenode, haruskah saya melewati node tertentu ke presenter dan kemudian dari situ presenter akan menentukan data apa yang dibutuhkan dan kemudian menanyakan model untuk data itu, sebelum menampilkannya kembali ke tampilan?

Jika memungkinkan, saya akan meneruskan semua data yang diperlukan untuk menampilkan pohon dalam satu tampilan dalam satu bidikan. Tetapi jika beberapa data terlalu besar untuk diteruskan dari awal atau jika sifatnya dinamis dan membutuhkan "snapshot terbaru" dari model (melalui presenter), maka saya akan menambahkan sesuatu seperti event LoadNodeDetailsEventHandler LoadNodeDetailsantarmuka tampilan, sehingga presenter dapat berlangganan padanya, mengambil detail node di LoadNodeDetailsEventArgs.Node(mungkin melalui beberapa jenis ID-nya) dari model, sehingga tampilan dapat memperbarui detail node yang ditampilkan saat delegasi event handler kembali. Perhatikan bahwa pola asinkron ini mungkin diperlukan jika pengambilan data mungkin terlalu lambat untuk pengalaman pengguna yang baik.

Johann Gerell
sumber
3
Saya tidak berpikir bahwa Anda harus memisahkan tampilan dan presenter. Saya biasanya memisahkan model dan penyaji, meminta penyaji mendengarkan peristiwa model dan bertindak sesuai (memperbarui tampilan). Memiliki penyaji dalam tampilan memudahkan komunikasi antara tampilan dan penyaji.
kasperhj
11
@lejon: Anda mengatakan bahwa memiliki penyaji dalam tampilan memudahkan komunikasi antara tampilan dan penyaji , tetapi saya sangat tidak setuju. Sudut pandang saya adalah ini: Ketika view mengetahui tentang presenter, maka untuk setiap view event view harus memutuskan metode presenter mana yang tepat untuk dipanggil. Itu adalah "2 poin kompleksitas", karena tampilan tidak benar-benar mengetahui peristiwa tampilan mana yang sesuai dengan metode penyaji mana . Kontrak tidak menentukan itu.
Johann Gerell
5
@lejon: Jika, sebaliknya, tampilan hanya menampilkan peristiwa yang sebenarnya, maka penyaji sendiri (yang tahu apa yang ingin dilakukannya saat peristiwa tampilan terjadi) hanya berlangganan untuk melakukan hal yang benar. Itu hanya "1 poin kompleksitas", yang dalam buku saya dua kali lebih baik dari "2 poin kompleksitas". Secara umum, lebih sedikit kopling berarti lebih sedikit biaya pemeliharaan selama menjalankan proyek.
Johann Gerell
9
Saya juga cenderung menggunakan presenter encapsulated seperti yang dijelaskan di link ini lostechies.com/derekgreer/2008/11/23/… di mana tampilan adalah satu-satunya pemegang presenter.
ak3nat0n
3
@ ak3nat0n: Sehubungan dengan tiga gaya MVP yang dijelaskan di tautan yang Anda berikan, saya yakin jawaban dari Johann ini mungkin paling selaras dengan gaya ketiga yang diberi nama Gaya Penyaji Mengamati : "Manfaat gaya Pengamat Presenter adalah bahwa itu benar-benar memisahkan pengetahuan Presenter dari View sehingga View tidak terlalu rentan terhadap perubahan dalam Presenter. "
DavidRR
11

Presenter, yang berisi semua logika dalam tampilan, harus menanggapi tombol yang diklik seperti yang dikatakan @JochemKempe . Dalam istilah praktis, klik tombol event handler memanggil presenter.OpenFile(). Presenter kemudian dapat menentukan apa yang harus dilakukan.

Jika memutuskan bahwa pengguna harus memilih file, ia memanggil kembali ke tampilan (melalui antarmuka tampilan) dan membiarkan tampilan, yang berisi semua teknis UI, menampilkan OpenFileDialog. Ini adalah perbedaan yang sangat penting karena penyaji tidak boleh melakukan operasi yang terkait dengan teknologi UI yang digunakan.

File yang dipilih kemudian akan dikembalikan ke presenter yang melanjutkan logikanya. Ini mungkin melibatkan model atau layanan apa pun yang harus menangani pemrosesan file.

Alasan utama menggunakan pola MVP, imo adalah untuk memisahkan teknologi UI dari logika tampilan. Jadi, penyaji mengatur semua logika sementara tampilan membuatnya tetap terpisah dari logika UI. Ini memiliki efek samping yang sangat bagus membuat penyaji dapat diuji sepenuhnya.

Pembaruan: karena penyaji adalah perwujudan dari logika yang ditemukan dalam satu tampilan tertentu , hubungan tampilan-penyaji adalah IMO hubungan satu-ke-satu. Dan untuk semua tujuan praktis, satu contoh tampilan (katakanlah Formulir) berinteraksi dengan satu contoh penyaji, dan satu contoh penyaji berinteraksi hanya dengan satu contoh tampilan.

Yang mengatakan, dalam implementasi MVP dengan WinForms, presenter selalu berinteraksi dengan tampilan melalui antarmuka yang mewakili kemampuan UI tampilan. Tidak ada batasan tentang tampilan apa yang mengimplementasikan antarmuka ini, sehingga "widget" yang berbeda dapat mengimplementasikan antarmuka tampilan yang sama dan menggunakan kembali kelas penyaji.

Peter Lillevold
sumber
Terima kasih. Jadi dalam metode presenter.OpenFile (), seharusnya tidak memiliki kode untuk menampilkan openfiledialog? Sebaliknya itu harus kembali ke tampilan untuk menunjukkan jendela itu?
Darren Young
4
Benar, saya tidak akan pernah membiarkan presenter membuka kotak dialog secara langsung, karena itu akan merusak pengujian Anda. Baik offload itu ke tampilan atau, seperti yang telah saya lakukan di beberapa skenario, memiliki kelas "FileOpenService" terpisah menangani interaksi dialog yang sebenarnya. Dengan cara itu Anda dapat memalsukan layanan pembukaan file selama pengujian. Menempatkan kode tersebut dalam layanan terpisah dapat memberi Anda efek samping kegunaan ulang yang bagus :)
Peter Lillevold
2

Penyaji harus bertindak atas permintaan dan menunjukkan jendela openfiledialog seperti yang Anda sarankan. Karena tidak ada data yang diperlukan dari model, penyaji dapat, dan seharusnya, menangani permintaan tersebut.

Mari asumsikan Anda membutuhkan data untuk membuat beberapa entitas dalam model Anda. Anda dapat meneruskan aliran melalui ke lapisan akses tempat Anda memiliki metode untuk membuat entitas dari aliran, tetapi saya sarankan Anda menangani penguraian file di presenter Anda dan menggunakan konstruktor atau metode Buat per entitas dalam model Anda.

JochemKempe
sumber
1
Terima kasih atas tanggapannya. Selain itu, apakah Anda memiliki satu penyaji untuk tampilan tersebut? Dan penyaji itu menangani permintaan tersebut, atau jika data diperlukan, maka ia akan mendelegasikan ke sejumlah kelas model yang bertindak berdasarkan permintaan tertentu? Apakah itu cara yang benar? Terima kasih lagi.
Darren Young
3
Tampilan memiliki satu penyaji tetapi penyaji bisa memiliki beberapa tampilan.
JochemKempe