poniedziałek, 12 grudnia 2011

Pasywny widok, czyli MVC w środowisku eventowym

     Tak jak zapowiedziałem w poprzednim wpisie, chciałbym zaproponować architekturę aplikacji opartych na Windows Forms (lub na innym środowisku eventowym, np. na Javowym Swingu), która rozdzieli prezentację od logiki oraz umożliwi testowanie jednostkowe.
     Ktoś, kto na co dzień pisze aplikacje webowe może się w tym miejscu zdziwić, bo przecież w kontekście aplikacji webowych oczywistą oczywistością jest to, że jeśli chce się rozdzielić logikę od prezentacji należy zastosować klasyczny wzorzec projektowy MVC. Dlaczego nie jest tak w przypadku aplikacji okienkowych? Żeby to wyjaśnić, przypomnę, jak działa aplikacja webowa stosująca MVC.
     Do serwera przychodzi żądanie HTTP. Na podstawie adresu URL budowany jest odpowiedni kontroler, a na podstawie parametrów (najczęściej zawartych w żądaniu HTTP POST) budowany jest model. Następnie model jest przekazywany do kontrolera, który zmienia jego stan, a potem przekazuje model do widoku. Widok buduje od zera swoją zawartość na podstawie stanu modelu. Na koniec zbudowany widok jest odsyłany do klienta, a aplikacja może "wyrzucić do śmieci" wszystkie obiekty utworzone do obsłużenia żądania.
     Taki cykl życia nie nadaje się zupełnie do aplikacji okienkowych, bo musielibyśmy po każdym najdrobniejszym kliknięciu budować i renderować od zera całą warstwę prezentacji. Martin Fowler opisał na swojej stronie wzorzec Passive View (Pasywny widok), który jest wersją MVC nadającą się do aplikacji okienkowych.
     Wzorzec jest bardzo ciekawy, ale wg mnie, sposób w jaki pan Fowler go opisał jest dość zagmatwany, a przedstawione diagramy UML są dla mnie nieczytelne. Osobiście preferuję przedstawianie wzorców projektowych za pomocą działającego kodu i właśnie dlatego postanowiłem napisać prostą aplikację demonstrującą pasywny widok. Można ją pobrać stąd.
     Aplikacja jest bardzo prosta: wpisujemy imię i nazwisko w pole tekstowe, a gdy napis nie składa się z dwóch słów rozpoczynających się z dużych liter, to wyświetlany jest komunikat o błędzie.



     Jeśli chodzi o architekturę, to cały trick polega na tym, że klasa-widok nie wykonuje sama żadnych czynności i nie ma żadnych pól (nie licząc wygenerowanych przy użyciu Designera pól wizualnych kontrolek). W szczególności widok nie wie nic o modelu - i to jest zasadnicza różnica w stosunku do webowego MVC. Do kodu widoku dodaje się tylko settery i metodę podpinającą metody obserwujące zdarzenia na widoku (należące do kontrolera).
     Natomiast kontroler powinien być napisany tak, żeby mógł działać poprawnie nawet, gdy nie ma żadnej wizualnej reprezentacji. Dzięki temu można go przetestować jednostkowo, a to jest bardzo duża zaleta. W tym miejscu przypomniała mi się sytuacja, gdy mój przełożony robił złączenie dwóch rozgałęzień na SVNie i gdy przy niektórych ważnych kontrolkach pojawił się napis "merged" powiedział, że przydałaby mu się wódka na odwagę przed wrzuceniem złączonego kodu "na produkcję". Gdyby miał do dyspozycji testy jednostkowe złączonych klas, mógłby je odpalić i już by wiedział, czy złączenie było wykonane poprawnie.
     Do omówienia został jeszcze tylko model. Jest on najprostszym elementem tej układanki. Jedyne, o czym warto napisać, to że dobrze jest napisać model w oparciu o wzorzec Obserwator, czyli niech model powiadamia o zmianach swojego stanu, dzięki czemu kontroler nie musi sprawdzać go w sposób czynny.

5 komentarzy:

  1. Bardzo proszę o wplatanie fragmentów kodu, żeby tekst był bardziej zrozumiały, bo temat godny uwagi :)

    OdpowiedzUsuń
  2. Hej :) Ciekawy post, elastyczność zapewne jest ogromna choć wada wg mnie to taka, że mimo banalnego kodu wykonywalnego (sprawdzenie regex) kod aplikacji stał się dość skomplikowany.

    Szczerze nie znam się na wzorcach, a niżej przedstawiony kod jest wzięty z własnych doświadczeń i w sumie jestem ciekawy, czy coś takiego funkcjonuje "masowo". Osobiście od pewnego czasu zacząłem stosować taką wersję, jest dość prosta. Przykład został okrojony (np. z integracją z dostępem do danych, osobiście jestem zwolennikiem Repository).

    public interface IFormView
    {
    event EventHandler TxtEvent;
    string ErrorTxt {set; }
    string Txt { get; }
    }

    public partial class Form1 : Form, IFormView
    {
    FormController controller;

    public Form1()
    {
    InitializeComponent();
    controller = new FormController(this);
    }


    public string Txt
    {
    get { return txtName.Text; }
    }

    public event EventHandler TxtEvent
    {
    add { txtName.TextChanged += value; }

    remove { txtName.TextChanged -= value; }
    }


    public string ErrorTxt
    {
    set { lblError.Text = value; }
    }
    }

    public class FormController
    {
    private IFormView _view;

    public FormController(IFormView view)
    {
    _view = view;
    _view.TxtEvent += _txtEvent;
    }

    void _txtEvent(object sender, EventArgs e)
    {
    var parse = new Regex(@"^[A-Z][a-z]+ [A-Z][a-z]+$");

    _view.ErrorTxt = !parse.IsMatch(_view.Txt) ? "Nieprawidłowe imię i nazwisko" : string.Empty;
    }
    }

    Oczywiście nic nie stoi na przeszkodzie rozbudowania tego o dodatkowe interface, ale taki o to model wg mnie jest dość dobry, prosty i czytelny, choć z pewnością nie zapewnia aż takiej elastyczności.

    Pozdrawiam Tomek ;)

    OdpowiedzUsuń
  3. Do Tomka:
    Moją intencją było zaproponowanie architektury dla dużych i skomplikowanych aplikacji, a przykładowa aplikacja jest mała i prosta, bo to tylko demo.

    Wg mnie, Twoje rozwiązanie ma dwie zasadnicze wady:
    1. Widok jest odpowiedzialny za utworzenie kontrolera. W takiej małej aplikacji to nie jest problem, ale w prawdziwym programie kontroler będzie zależny od tony klas logiki biznesowej, więc widok będzie musiał również być bardzo rozbudowaną fabryką. Co więcej, jeśli dziedziczyłbyś z z takiego widoku, to w designerze dziedziczącego okienka kod fabrykujący byłby wywoływany (a to mogłoby oznaczać różne anomalia).
    2. Klasy w Twoim roziązaniu mają cykliczne zależności (widok ma referencję do kontrolera, a kontroler referencję do widoku). To może być problematyczne.

    OdpowiedzUsuń
  4. Jedna uwaga - MVC znany z weba to wersja przerobiona wzorca, a nie klasyczna :) Prawdziwe MVC jest właśnie w aplikacjach okienkowych.

    OdpowiedzUsuń
  5. Moi drodzy, jestem ciekawa waszej opinii. Lepiej przechodzić kursy czy zdecydować się na studia informatyczne np. na http://www.wseiz.pl/en/studia . Co będzie korzystniejsze, co umożliwi zdobycie lepszej pracy i lepszych kwalifikacji?

    OdpowiedzUsuń