Po raz pierwszy próbuję zaimplementować metodę MVP przy użyciu WinForms.
Próbuję zrozumieć funkcję każdej warstwy.
W moim programie mam przycisk GUI, który po kliknięciu otwiera okno dialogowe openfiledialog.
Więc używając MVP, GUI obsługuje zdarzenie kliknięcia przycisku, a następnie wywołuje presenter.openfile ();
Czy w ramach presenter.openfile () powinno to delegować otwarcie tego pliku do warstwy modelu, czy też ze względu na brak danych lub logiki do przetworzenia, powinno po prostu działać na żądanie i otworzyć okno openfiledialog?
Aktualizacja: Zdecydowałem się zaoferować nagrodę, ponieważ czuję, że potrzebuję dalszej pomocy w tej sprawie i najlepiej dostosowanej do moich konkretnych punktów poniżej, aby mieć kontekst.
OK, po przeczytaniu o MVP zdecydowałem się zaimplementować Widok pasywny. Efektywnie będę miał kilka kontrolek w Winform, które będą obsługiwane przez Prezenter, a następnie zadania delegowane do Modelu (ów). Moje konkretne punkty znajdują się poniżej:
Kiedy winform ładuje się, musi uzyskać widok drzewa. Czy mam rację sądząc, że widok powinien zatem wywołać metodę taką jak: presenter.gettree (), to z kolei oddeleguje do modelu, który pobierze dane do drzewa, utworzy i skonfiguruje, zwróci do prezenter, który z kolei przejdzie do widoku, który następnie po prostu przypisze go, powiedzmy, do panelu?
Czy to będzie to samo dla każdej kontroli danych w Winform, ponieważ mam również datagridview?
Moja aplikacja ma wiele klas modelu z tym samym zestawem. Obsługuje również architekturę wtyczek z wtyczkami, które muszą być ładowane podczas uruchamiania. Czy widok po prostu wywołałby metodę prezentera, która z kolei wywołałaby metodę ładującą wtyczki i wyświetlającą informacje w widoku? Która warstwa będzie wtedy kontrolować odwołania do wtyczek. Czy widok zawiera odniesienia do nich lub do prezentera?
Czy mam rację sądząc, że widok powinien obsługiwać każdą rzecz dotyczącą prezentacji, od koloru węzła drzewa, po rozmiar datagrid, itp.?
Myślę, że są to moje główne zmartwienia i jeśli rozumiem, jaki powinien być dla nich przepływ, myślę, że będzie dobrze.
Odpowiedzi:
To jest moje skromne podejście do MVP i Twoich konkretnych problemów.
Po pierwsze , wszystko, z czym użytkownik może wchodzić w interakcje lub po prostu być pokazywane, to widok . Prawa, zachowanie i cechy takiego widoku są opisane przez interfejs . Ten interfejs można zaimplementować za pomocą interfejsu użytkownika WinForms, interfejsu konsoli, interfejsu internetowego lub nawet żadnego interfejsu użytkownika (zwykle podczas testowania prezentera) - konkretna implementacja nie ma znaczenia, o ile jest zgodna z prawami interfejsu widoku .
Po drugie , widok jest zawsze kontrolowany przez prezentera . Prawa, zachowanie i cechy takiego prezentera są również opisane przez interfejs . Ten interfejs nie jest zainteresowany implementacją widoku konkretnego, o ile jest zgodny z prawami interfejsu widoku.
Po trzecie , ponieważ prezenter kontroluje swój widok, aby zminimalizować zależności, tak naprawdę nie ma żadnej korzyści z tego, że widok w ogóle wie cokolwiek o swoim prezencie. Istnieje uzgodniona umowa między prezenterem a widokiem i jest to określone w interfejsie widoku.
Konsekwencje Trzeciego to:
W przypadku Twojego problemu powyższy kod może wyglądać tak w nieco uproszczonym kodzie:
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(); } }
Oprócz powyższego zwykle mam
IView
interfejs podstawowy , w którym przechowujęShow()
i dowolny widok właściciela lub tytuł, z którego zwykle korzystają moje widoki.Na Twoje pytania:
1. Kiedy winform ładuje się, musi uzyskać widok drzewa. Czy mam rację sądząc, że widok powinien zatem wywołać metodę taką jak: presenter.gettree (), to z kolei oddeleguje do modelu, który pobierze dane do drzewa, utworzy go i skonfiguruje, zwróci do prezenter, który z kolei przejdzie do widoku, który następnie po prostu przypisze go, powiedzmy, do panelu?
2. Czy wyglądałoby to tak samo dla każdej kontroli danych w Winform, ponieważ mam również datagridview?
3. Moja aplikacja ma kilka klas modeli z tym samym zestawem. Obsługuje również architekturę wtyczek z wtyczkami, które muszą być ładowane podczas uruchamiania. Czy widok po prostu wywołałby metodę prezentera, która z kolei wywołałaby metodę ładującą wtyczki i wyświetlającą informacje w widoku? Która warstwa będzie wtedy kontrolowała odwołania do wtyczek. Czy widok zawiera odniesienia do nich czy do prezentera?
4. Czy mam rację sądząc, że widok powinien obsługiwać każdą rzecz dotyczącą prezentacji, od koloru węzła drzewa, po rozmiar datagrid, itp.?
A co z danymi dla klikniętych węzłów?
5. Jeśli po kliknięciu treenodes powinienem przejść przez określony węzeł do prezentera, a następnie na tej podstawie prezenter powinien dowiedzieć się, jakich danych potrzebuje, a następnie poprosić model o te dane, zanim zaprezentuje je z powrotem w widoku?
źródło
Prezenter, który zawiera całą logikę w widoku, powinien zareagować na kliknięcie przycisku, jak mówi @JochemKempe . W praktyce wywołuje funkcję obsługi zdarzenia kliknięcia przycisku
presenter.OpenFile()
. Prezenter może wtedy określić, co należy zrobić.Jeśli zdecyduje, że użytkownik musi wybrać plik, wywołuje z powrotem do widoku (za pośrednictwem interfejsu widoku) i pozwala widokowi, który zawiera wszystkie szczegóły techniczne interfejsu użytkownika, wyświetlić plik
OpenFileDialog
. Jest to bardzo ważne rozróżnienie, ponieważ prezenter nie powinien mieć możliwości wykonywania operacji związanych z używaną technologią UI.Wybrany plik zostanie następnie zwrócony prezenterowi, który kontynuuje swoją logikę. Może to dotyczyć dowolnego modelu lub usługi, które powinny obsługiwać przetwarzanie pliku.
Głównym powodem stosowania wzorca MVP, imo, jest oddzielenie technologii interfejsu użytkownika od logiki widoku. W ten sposób prezenter organizuje całą logikę, podczas gdy widok oddziela ją od logiki interfejsu użytkownika. Ma to bardzo fajny efekt uboczny polegający na tym, że prezenter jest w pełni testowalny jednostkowo.
Aktualizacja: ponieważ prezenter jest ucieleśnieniem logiki znajdującej się w jednym określonym widoku , relacja widok-prezenter jest relacją IMO jeden do jednego. Ze wszystkich praktycznych powodów jedna instancja widoku (powiedzmy Forma) współdziała z jedną instancją prezentera, a jedna instancja prezentera tylko z jedną instancją widoku.
To powiedziawszy, w mojej implementacji MVP z WinForms prezenter zawsze wchodzi w interakcję z widokiem poprzez interfejs reprezentujący możliwości interfejsu użytkownika widoku. Nie ma ograniczeń co do tego, który widok implementuje ten interfejs, dlatego różne „widżety” mogą implementować ten sam interfejs widoku i ponownie wykorzystywać klasę prezentera.
źródło
Prezenter powinien działać na końcu żądania, pokazując otwarte okno dialogowe, zgodnie z sugestią. Ponieważ od modelu nie są wymagane żadne dane, prezenter może i powinien obsłużyć żądanie.
Załóżmy, że potrzebujesz danych do utworzenia niektórych encji w modelu. Możesz albo przekazać koryto strumienia do warstwy dostępu, w której masz metodę tworzenia jednostek ze strumienia, ale sugeruję, abyś zajął się analizowaniem pliku w swoim prezenterze i użył konstruktora lub metody Create na jednostkę w swoim modelu.
źródło