poniedziałek, 20 lutego 2012

Obiekty vs. struktury danych

    Jestem właśnie w trakcie lektury "Czystego kodu" Roberta C. Martina (polecam wszystkim programistom). Chciałbym podzielić się pewnym spostrzeżeniem autora tej książki. Otóż jako zupełne przeciwieństwa stawia on kod obiektowy oraz kod proceduralny operujący na strukturach danych. Zasadnicza różnica polega na tym, że klasy ukrywają swoją implementację komunikując się ze światem zewnętrznym wyłącznie przez metody, podczas gdy struktury danych mają zupełnie jawną postać, przez co nie potrzebują żadnych metod.
Przykład kodu obiektowego:

interface IShape
{
    double Area();
}

class Square: IShape
{
    private Point topLeft;
    private double side;
        
    public double Area()
    {
        return side*side;
    }
}

class Circle: IShape
{
    private Point center;
    private double radius;
    
    public double Area()
    {
        return Math.PI*radius*radius;
    }
}

Przykład kodu używającego struktur danych:
class Square
{
    public Point TopLeft;
    public double Side;
}

class Circle
{
    public Point Center;
    public double Radius;
}

class AreaCalculator
{
    public double Area(object shape)
    {
        if(shape is Square)
        {
            var s = (Square)shape;
            return s.Side * s.Side;
        }
        if(shape is Circle)
        {
            var c = (Circle)shape;
            return Math.PI * c.Radius * c.Radius;
        }
        throw new NoSuchShapeException();
    }
}
    Autor zauważa przy tym, że łatwo jest dodać nową klasę obiektu albo nową operację na strukturach danych (wystarczy dopisać nowy kod, a w starym nie trzeba wprowadzać żadnych zmian), natomiast trudno jest dodać nową metodę obiektów albo nowy typ struktury danych (stary kod trzeba będzie zmienić w każdym miejscu). Ponadto zauważa, że gdy używa się hybrydowych typów, które mają jawną implementację (gettery i settery) oraz zbiór metod, to mamy kod posiadający wady obu styli programowania - wtedy jakakolwiek zmiana jest utrudniona.
    Trudno nie zgodzić się z Robertem Martinem. Chciałbym do tego wszystkiego dorzucić jeszcze swoje trzy grosze. Zwróćmy uwagę, że istnieje wiele sytuacji, gdy ukrycie implementacji jest mniej naturalnym rozwiązaniem niż jej ujawnienie. W sytuacjach, gdy potrzeba funkcjonalności np.:

  • przechowania danych odczytanych z bazy,
  • przechowania danych odczytanych z XMLa,
  • przechowywania danych wyświetlanych użytkownikowi na formularzu,
  • przesyłania danych przez sieć,
  • itp.

ujawnienie implementacji jest co najmniej bardziej naturalne, jeśli nie konieczne. Gdy korzystamy z mechanizmów takich, jak serializacja XML, to sam mechanizm wymusza na nas ujawnienie implementacji - wszystkie zmienne instancyjne muszą wtedy mieć gettery i settery. I nie łudźmy się w tym miejscu, że gettery / settery ukrywają implementację - to nie prawda. Dam konia z rzędem temu, kto powie mi, jaką cechą istotną z punktu widzenia projektowania różni się kod:
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
od kodu:
class Person
{
    public string FirstName;
    public string LastName;
}
Widzicie jakąś różnicę? Ja nie widzę (i nie ma tutaj znaczenia, że w przykładzie powyżej użyłem właściwości automatycznych, a nie zwyczajnych z prywatnym polem pod spodem - to nic nie zmienia - implementacja jest ujawniona).
    Jaki praktyczny wniosek płynie z powyższych obserwacji? Według mnie, po pierwsze taki, że nie należy kurczowo trzymać się żadnego z góry określonego paradygmatu, tylko dostosować swój styl programowania do danego kontekstu. Jak pokazuje praktyka, nie każdy kontekst zachęca do stylu obiektowego. Ale gdy w danym module aplikacji obierze się dany styl, to należy być konsekwentnym i nie należy mieszać dwóch różnych styli (ani oszukiwać się, że klasa w stylu bean jest obiektowa).

poniedziałek, 9 stycznia 2012

Programowanie deklaratywne vs programowanie imperatywne

    Na wstępie skrótowo wytłumaczę, co rozumiem pod pojęciami zawartymi w tytule. Programowanie imperatywne polega na tym, że kod programu składa się z instrukcji (czyli rozkazów). Najbardziej typowe instrukcje to przypisania, wywołania procedur / metod, instrukcje warunkowe czy pętle. W programowaniu imperatywnym zmieniający się stan programu jest czymś naturalnym, a niedeterministyczne działanie procedur i metod - czymś często występującym.
    Zupełnie innym podejściem jest programowanie deklaratywne. W tym przypadku nie pisze się, co po kolei program ma robić, tylko jaką wartość chce się uzyskać. Czyli program deklaratywny nie jest zbiorem instrukcji, tylko zbiorem deklaracji właściwości obiektu, który chcemy uzyskać na wyjściu.
    Wśród powszechnie używanych języków generalnego zastosowania jest niewiele języków czysto deklaratywnych (czyli takich, w których nie ma instrukcji ani skutków ubocznych). Jedynym, jaki ja znam jest Haskell. Niemniej jednak jest cała masa języków prawie :-) deklaratywnych, które preferują deklaracje wartości nad instrukcjami, ale pozostawiają w swojej składni instrukcje np. żeby uprościć operacje I/O. Do takich języków należy cała "rodzina" ML (czyli m.in. OCaml i F#) a także kompilowana do JVM Scala.
    Język C# był na początku czysto imperatywny. Na szczęście, w każdej kolejnej wersji nabierał coraz więcej możliwości programowania funkcjonalnego (czyli deklaratywnego). Programując w najnowszej wersji (C# 4.0) często stoimy przed wyborem, czy zaimplementować coś imperatywnie, czy deklaratywnie.
    Jakiś czas temu zastanawiałem się, jak te dwa style przekładają się na wydajność programu i postanowiłem trochę poeksperymentować...
    Postanowiłem zaimplementować w miarę prosty algorytm na dwa sposoby, a następnie porównać czasy wykonania. Do implementacji wybrałem funkcję, która bierze na wejściu dwie listy i zwraca informację, czy druga lista jest podzbiorem pierwszej.
    Najprostsza implementacja imperatywna byłaby następująca:

public bool IsSubset<T>(List<T> first, List<T> second)
{
    foreach(T elem in second)
    {
        if(!first.Contains(elem))
            return false;
    }
    return true;
}

    Powyższe rozwiązanie ma złożoność kwadratową (a w zasadzie: prostokątną :-) ). Jeśli pierwsza lista będzie miała 100 000 elementów, a druga będzie jej 10 000-elementowym podzbiorem, to na moim komputerze taka metoda wykona się w czasie ok. 39 sekund. Typowy, średnio lubiący swoją pracę programista nawet nie będzie się zastanawiał, czy można to napisać lepiej, bo:
  1. "terminy gonią",
  2. "nie chce mi się",
  3. "czas procesora jest tańszy niż czas programisty",
  4. ...
    Trochę lepszy programista pomyśli, że da się napisać implementację działającą w czasie n * log n:
public bool IsSubsetBinarySearch<T>(List<T> first, List<T> second) 
    where T: IComparable<T>
{
    var array = new T[first.Count];
    first.CopyTo(array);
    Array.Sort(array);
    foreach (var elem in second) {
        if(Array.BinarySearch(array, elem) < 0)
            return false;
    }
    return true;            
}

Powyższe rozwiązanie dla tych samych danych "wypluje" wynik po czasie ok. 3,14 sekund. Mało kto szukałby w takiej sytuacji lepszego algorytmu, bo:
    a.  "terminy gonią",
    b.   ...
    z.  "klientowi nic się nie stanie, jak poczeka kilka sekund"
    Nie jest to jednak rozwiązanie optymalne, bo istnieje lepsza, liniowa implementacja:
public bool IsSubsetHashSet<T>(List<T> first, List<T> second)
{
    var hs = new HashSet<T>(first);
    foreach (var elem in second) 
    {
        if(!hs.Contains(elem))
            return false;
    }
    return true;
}

    Kod, który znajduje się powyżej to są już granice moich imperatywnych zdolności :-) Zaimplementowana w taki sposób metoda zakończyła działanie po 0,047 sekundy.
    Teraz przejdźmy do wersji deklaratywnej (używającej Linq to Objects):
public bool IsSubsetLinq<T>(List<T> first, List<T> second)
{
    return second
        .Except(first)
        .Count() == 0;
}

    Powyższa metoda jest wręcz matematyczną definicją problemu, jaki sobie postawiłem. Jest krótsza, prostsza i czytelniejsza nawet od najbardziej naiwnej wersji imperatywnej. A działa zaskakująco dobrze: kończy działanie po 0,063 sekundy! Co prawda, jest to troszkę więcej niż w przypadku optymalnej implementacji imperatywnej, ale:
  • wygląda na to, że asymptotyczny czas działania jest taki sam (nieznaczna różnica),
  • czas poświęcony na napisanie kodu był bardzo krótki,
  • taki kod jest na tyle prosty, że może go napisać nawet słaby programista.
    Ten ostatni argument jest według mnie, szczególnie istotny. Programowanie funkcyjne jest, po przełamaniu początkowych trudności, po prostu łatwiejsze. Jeśli z niego zrezygnujemy na rzecz wyżyłowanych imperatywnych implementacji, to się chyba przejedziemy... Praktyka pokazuje (takie mam wrażenie), że łatwiej jest znaleźć dziewicę w akademiku niż optymalną implementację w dużym, imperatywnie napisanym systemie informatycznym :-)

poniedziałek, 2 stycznia 2012

Darmowe IDE do F#

    Ostatnio szukałem narzędzia, które pozwoli mi programować w domu w F#. Oczywiście, nie chciałem płacić za żadną licencję, ani tym bardziej pisać w kodu w notatniku (interesowało mnie IDE). Na razie w ofercie Microsoftu nie ma czegoś takiego, jak Visual F# Express Edition (pomimo, że F# jest w tej chwili jednym z trzech, obok C# i VB, głównych języków .NETa), więc postanowiłem sprawdzić inne darmowe środowiska.
    Na pierwszy ogień poszedł SharpDevelop. Środowisko to rzeczywiście pozwala na tworzenie projektów F#, ale nie oferuje żadnych narzędzi, do których przyzwyczajony jest każdy programista. Nie ma ani podpowiadania składni, ani dokumentacji funkcji po najechaniu na nią myszką, ani żadnych innych ułatwień. Jedyne, co mamy, to edytor kodu z (zawierającym bugi) kolorowaniem składni oraz integracja z kompilatorem. Według mnie, to zdecydowanie za mało, jak na IDE.
    Później znalazłem informacje, że MonoDevelop ma wtyczkę do F#, która wspiera podpowiadanie składni. Niestety, nie udało mi się jej zainstalować (a próbowałem na wszelkie możliwe sposoby). Z tego, co zdążyłem się zorientować wynika, że do najnowszej windowsowej wersji MonoDevelop wdarł się bug uniemożliwiający instalację wyżej wymienionej wtyczki.
    W zaistniałej sytuacji spróbowałem trzeciej opcji, która polegała na zainstalowaniu Visual Studio 2010 Shell, czyli czystego VS bez wsparcia dla żadnego języka, a następnie zainstalowaniu F#. Po tym zabiegu VS zaczął wspierać F# w pełnym zakresie (identycznie, jak w płatnej wersji), czyli osiągnąłem swój cel :-)
    Podobno można również programować w F# przy użyciu Emacsa, ale tej opcji nawet nie sprawdzałem, bo nie jestem przyzwyczajony do tego środowiska...

środa, 28 grudnia 2011

Czy null jest potrzebny?

     W komentarzu do poprzedniego posta Bartosz napisał, że według niego nowo utworzony obiekt z wynullowanymi polami nie zawsze jest błędem i czasami może mieć sens. Nie zgadzam się z tym zupełnie. Powiem więcej: wg mnie, w językach wysokiego poziomu (chodzi mi o takie, gdzie już nie musimy ręcznie alokować i zwalniać pamięci) wartość null nie ma w ogóle racji bytu!
     Wartość NULL występuje w językach takich jak C, czy C++ właśnie po to, żeby reprezentować pusty wskaźnik. Natomiast w językach takich, jak C# czy Java nie zarządza się ręcznie pamięcią, a null w zasadzie nie ma żadnego sensownego uzasadnienia. Ktoś może w tym miejscu zaprotestować i powiedzieć, że null ma np. wiele zastosowań przy implementacji różnego rodzaju kolekcji. Odpowiem na to, że kolekcje da się w językach wysokiego poziomu zaimplementować bardziej sensownie. Zaprezentuję to później na przykładzie języka F#.
     Zanim to zrobię, pokażę cztery linijki kodu, które zszokują zatwardziałych Jawowców i C-Sharpowców :-) W F# poniższy kod:

type MyClass()=
  
member x.Foo () = ()

let mutable x = new MyClass()
<- null

...nie skompiluje się! Kompilator powie, że null nie jest wartością klasy MyClass. Analogicznie jest w przypadku każdej innej klasy zdefiniowanej w tym języku: null nie jest poprawną wartością. W F# słowo kluczowe null występuje tylko i wyłącznie po to, żeby móc korzystać ze standardowych bibliotek .NET-owych.
     Biblioteki F# zamiast wartości null używają unii dyskryminowanych (ang. discriminated union). Najprostszą z nich jest option, który jest zdefiniowany tak:

type 'a option =
  None
  
| Some of 'a

Czyli albo nie mamy wartości (None) albo ją mamy (Some). Option może być użyty w funkcji w następujący sposób:

let makeList opt =
  match opt with
    | None -> []
    | Some x -> [x]

Dla niewtajemniczonych: powyższa funkcja zwraca pustą listę dla braku wartości i jednoelementową listę, gdy poda się wartość.
A co z kolekcjami? Otóż jest równie prosto. Na przykład lista łączona, którą w C# zdefiniowalibyśmy tak:

public class LinkedList<T>
{
    private T head;
    private LinkedList<T> tail;
    [..]
}

w językach funkcyjnych zdefiniowana byłaby przy użyciu unii dyskryminowanej w taki sposób:

type 'a LinkedList =
  Empty
  | NonEmpty of 'a * 'a LinkedList 
 
Według mnie ta druga definicja jest o wiele jaśniejsza. Rekurencyjna definicja listy łączonej jest tu podana wprost - zupełnie jak w definicjach matematycznych. Natomiast w C# (lub w Jawie) trzeba żonglować referencjami i nullami a przez to kod przestaje tu być zrozumiały sam przez się.

poniedziałek, 19 grudnia 2011

Pasywny widok cd. i antywzorzec Magiczna Metoda

    W poprzednim poście opisałem wzorzec projektowy Pasywny Widok. Tomek w komentarzu do wpisu słusznie zauważył, że mój kod jest trochę przekombinowany. Ponadto gdy spojrzałem jeszcze raz na ten kod zauważyłem w nim pewien smrodek (nawiasem mówiąc, jak powinno się przetłumaczyć na polski angielskie "code smell"?). Myślę, że warto wrócić do tamtego kodu raz jeszcze.
    Najpierw opiszę, o co chodzi z "przekombinowaniem". Zdefiniowałem interfejs INameModel:


public interface INameModel
{
    string Name { getset; }
    event Action<string> NameChanged;
}


A potem konkretną klasę:


public class NameModel: INameModel
{
    
string name;
  
    
public string Name {
        
get { return name; }
        
set
        {
            name = 
value;
            
OnNameChanged(name);
        }
    }
  
    
public event Action<string> NameChanged;
  
    
protected virtual void OnNameChanged(string obj)
    {
        
if (NameChanged != null) {
            
NameChanged(obj);
        }
    }
}

 
    Po przemyśleniu uznałem, że choć nie jest to błąd, jest to niepotrzebny przerost formy nad treścią. Generalnie, interfejsy mają sens w dwóch przypadkach:
  1. gdy klasa implementująca jest serwisem,
  2. gdy dana implementacja klasy-wartości nie jest jedyną słuszną implementacją (np. interfejs IList jest implementowany przez LinkedList i ArrayList).
Wydaje się, że w przypadku klasy NameModel nie zachodzi żaden z powyższych przypadków. Sklasyfikowałbym :-) ją jako typową klasę-wartość. Odpalenie zdarzenia po wywołaniu settera to chyba nie jest żadna logika. Poza tym, w przypadku stosowania wzorca Pasywny Widok, dodalibyśmy sobie snippet generujący takie kilkanaście linii kodu. A jak coś jest generowane przez snippet, to to nie jest logika, tylko składnia języka :-) Czy ktoś czuje się jeszcze nieprzekonany, że NameModel nie różni się zasadniczo od takich klas, jak String czy Uri? Ja sam siebie przekonałem :-)
    To teraz uderzę się w piersi i opiszę smrodek, który napisałem. Użyłem antywzorca, który nazwałbym Magiczną Metodą (jeśli ktoś zna fachową nazwę, proszę o komentarz). Polega on na tym, że obiekt nie jest gotowy do użycia pomimo tego, że został legalnie utworzony przy użyciu konstruktora. Po prostu, konstruktor nie inicjuje wszystkich pól zostawiając część wynullowanymi. Na konkretnym przykładzie z poprzedniego postu:


public class NameController: INameController
{
    
private readonly INameValidator validator;
    
private INameModel model;
    
private INameView view;
  
    
public NameController(INameValidator validator)
    {
        
this.validator = validator;
    }
  
    
public void StartControl(INameModel model, INameView view)
    {
        
this.view = view;
        
this.model = model;
        view.
RegisterObserver(this);
        
RegisterFeedback();
    }
  
    
private void RegisterFeedback()
    {
        model.NameChanged += newName =>
            view.ErrorMessage = validator.
ErrorMessage(newName);
    }      
  
    
public string Name {
        
set {
            model.Name = 
value;
        }
    }

 
W powyższej klasie, dopóki nie wywołamy magicznej metody StartControl, nie możemy wywoływać innych metod, bo będziemy dostawać NullReferenceException. A tak nie powinno być w żadnym wypadku.
Kontroler powinien wyglądać albo tak:


public class NameController: INameController
{
    
private readonly INameValidator validator;
    
private readonly INameView view;
    
private readonly NameModel model = new NameModel();
  
    
public NameController(
        INameValidator validator, INameView view)
    {
        
this.validator = validator;
        
this.view = view;
    }
  
    
public void StartControl()
    {
        view.
RegisterObserver(this);
        
RegisterFeedback();
    }
    [..]
}



albo, ewentualnie, tak:


public class NameController<TView>: INameController
    where TView: INameView, new()
{
    private readonly INameValidator validator;
    private readonly INameView view = new TView();
    private readonly NameModel model = new NameModel();
  
    public NameController(INameValidator validator)
    {
        this.validator = validator;
    }
  
    public void StartControl()
    {
        view.RegisterObserver(this);
        RegisterFeedback();
    }
    [..]

}


    W powyższych kawałkach kodu widać, że widok również traktujemy jako wartość, a nie serwis, ale w przeciwieństwie do modelu, chcielibyśmy żeby był podmienialny jakimś dublerem testowym (bo prawdziwy widok jest ciężkim i wolnym obiektem; patrz: punkt b) na początku wpisu).


PS
Jeśli ktoś nie spotkał się z pojęciem dublera testowego, odsyłam do strony Martina Fowlera, który chyba jako pierwszy użył tego terminu.
W tym wpisie odwoływałem się do klasyfikacji obiektów na serwisy i wartości. Jeśli taki podział nie jest jasny, proszę dać znać w komentarzu - w razie potrzeby napiszę o tym coś więcej.

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.

poniedziałek, 5 grudnia 2011

Windows Forms i pokusa dwukliku

    Biblioteka Windows Forms została napisana w taki sposób, że komponenty przekazują informacje o zmianie swojego stanu przy użyciu zdarzeń (ang. events). Fachowo taką architekturę określa się wzorcem projektowym Obserwator. Jest to bardzo fajny wzorzec, bo pozwala na rozdzielenie warstwy prezentacji od logiki biznesowej. Logika biznesowa podpinana jest pod zdarzenia okienek i voilà! - mamy gotową aplikację w architekturze warstwowej.
    Żeby powyższe optymistyczne założenie stało się prawdziwe musi być spełniony jeden warunek: programista musi zaimplementować logikę biznesową POZA kodem kontrolki. Niestety, Visual Studio bardzo mocno kusi programistę, żeby wrzucił wszystko do kodu kontrolki: gdy dwukliknie się dowolną kontrolkę w designerze VS, środowisko samo wygeneruje nam kod do obsługi zdarzenia WEWNĄTRZ kodu kontrolki. Na dodatek ustawi kursor w ciele nowo wygenerowanej metody. Podświadomy przekaz od Microsoftu jaki dociera do wielu programistów jest następujący: „that's the way it should be done” :-(
    Chciałbym jeszcze zwrócić uwagę, że wzorzec projektowy Obserwator zupełnie traci swój sens i swoje zalety, gdy obiekt obserwowany jest równocześnie obserwatorem (polecam przeczytać zalety Obserwatora wymienione na Wikipedii).
    Żeby nie było, że jestem jakimś krytykującym Microsoft szpiegiem z krainy Javy, dodam jeszcze że zupełnie analogiczna sytuacja ma miejsce w javowym Swingu używanym w NetBeansie. Po dwukliknięciu np. JButtona generowana jest metoda obsługująca zdarzenie w kodzie klasy, która powinna być odpowiedzialna wyłącznie za renderowanie kontrolek.
    W kolejnym wpisie na tym blogu postaram się zaproponować taką architekturę aplikacji opartych na Windows Forms, która rozdzieli prezentację od logiki oraz umożliwi testowanie jednostkowe (no tak, zapomniałem dodać, że w przypadku wszystko robiących kontrolek możemy zapomnieć o testach jednostkowych, ale to chyba oczywiste?).



PS Wg mnie Visual Studio powinno generować takie metody po dwukliknięciu kontrolki w designerze:
private void button1_Click(object sender, EventArgs e)
{
// TODO: if your programming skills let you write
// multi-class solutions:
// remove this method & put your logic elsewhere
// otherwise:
// code here on your own responsibility
}