Friday 3 February 2006

Singleton w C#

Chyba najczęściej stosowany wzorzec projektowy. Bardzo często też implementowany błędnie - większość przykładów dostępnych w sieci nie bierze zupełnie pod uwagę wielowątkowości. Przykład na MSDN też nie jest idealny. Dobra, najpierw działający kod:


/// <summary></summary>
public sealed class Singleton
{
#region Singleton
/// <summary>ukryty konstruktor</summary>
private Singleton()
{}

/// <value>Dostep do instancji singletona</value>
public static Singleton Instance
{ get { return InstaceHolder.instance; } }

/// <summary>klasa wewnętrzna przechowująca instancję</summary>
class InstaceHolder
{
/// <summary>konstruktor statyczny,
/// by kompilator NIE oznaczyl klasy jako BeforeFieldInit</summary>
static InstaceHolder() {}

/// <summary>instancja singletona</summary>
internal static readonly Singleton instance = new Singleton();
}
#endregion
}

I po kolei, o co w tym chodzi. Sama klasa oznaczona jako sealed, czyli nie można z niej dziedziczyć. Nie chcemy, by ktos stworzył klasę potomną, która będzie pozwalała na utworzenie wielu instancji. Funkcjonalność zbliżoną do polimorfizmu może nam łatwo dostarczyć np. wzorzec State.


Konstruktor prywatny, żeby nikt nam "na boku" nie utworzył instancji klasy.


Własność publiczna Instance - jedyny dostęp do singletona. Metody niestatyczne będzie się w nim wywoływać przez Singleton.Instance.MojaMetoda();


Klasa wewnętrza pozwala nam na leniwe, czyli opóźnione utworzenie instancji. Dopiero pierwsze odwołanie do Singleton.Instance utworzy obiekt. Kostruktor statyczny w InstanceHolder wymusza na kompilatorze, by nie oznaczał klasy jako BeforeFieldInit. To znaczy, że pola statyczne zostaną zainicjowane dopiero przy pierwszym odwołaniu do klasy (dostęp do statycznego pola/metody lub utworzenie instancji), a nie "na zapas". Uwaga - chodzi o pola/metody klasy InstanceHolder, dostęp do statycznych pól czy metod Singletona nie spowoduje zainicjalizowania go.


Inicjalizacja singletona powinna trafić do prywatnego konstruktora, a nie statycznego. W ogóle - ze statycznymi konstruktorami lepiej ostrożnie. InstanceHolder.instance mógłbym zainicjalizować w static InstaceHolder(), ne? Haczyk znajduje się w specyfikacji języka C#.

The static constructor for a class executes at most once in a given application domain.

Najwyżej raz oznacza, że może nie zostać wykonany ani razu. Tak się dzieje np. w niektórych wersjach Mono.



Kod pochodzi z yoda.arachsys.com



UPDATE: na MSDN znalazłem poprawny i elegancki kod singletona, z innym podejściem niż pokazane powyżej.

7 comments:

  1. Uaktualniłem przykładowy kod, by nadawał się do użycia przy jak najmniejszej ilości zmian. Wystarczy uzupełnić komentarze.

    ReplyDelete
  2. Wiesz? Zawsze interesowało mnie jak to jest jak mówię do "nieoświeconych" po jap czy chin albo jak im tłumaczę gramatykę tudzież znaki. Well, teraz wiem :D. I zastanawiam się tylko czy tak samo ich ta całkowita niewiedza i ignorancja cieszy tak jak mnie tu teraz :D. Jakbym nie wiedziała co potrafisz zrobić z moim komputerem to bym była pod wrazeniem ;P ;))) hehe.

    ReplyDelete
  3. Singleton jest używany do tego by instancja danej klasy była tylko jedna, np. udostepniając bibliotekę innym programistom, umożliwiając pisanie wtyczek do programu itp. Nawet przy jednowątkowych aplikacjach singleton jest przydatny. Dlatego uważam, że obsługa wielowątkowości nie należy do "specyfikacji" tego wzorca i nie można mówić o błędnej implementacji tego wzorca która jej nie uwzględnia.

    Zaimplementuj teraz podaną przez ciebie metodę w C++. Mówiąc o powszechności użytej metody w MSDN chodziło mi o jej latwe zastosowanie w wielu jezykach. Jest łatwa i czytelna w porównaniu do tej przedstawionej przez Ciebie. Np nie wiedziałem o takim zachowaniu sie klasy statycznej w C#.

    Jednak masz rację, że sposób podany przez Ciebie jest wydajniejszy i naprawde mi się podoba choć nieco bardziej "skomplikowany".

    ReplyDelete
  4. Dammit, zaraz muszę prowadzącym powiedzieć, że pomylili się przepuszczając mnie na egzaminie :P Za założenia jednowątkowy? Kiedy mamy jeden wątek singleton niezbędny nie jest, przecież nie ma "wyścigu" między wątkami, dokładnie wiadomo, kiedy ta jedna jedyna dozwolona instancja zostanie utworzona. Można obiekt łatwo przekazywać między metodami, które go potrzebują. Jest to dużo trudniejsze przy wielu wątkach - i wszystkie zastosowania tego wzorca, z którymi się spotkałem w praktyce, używały go wielowątkowo.


    A sposób z MSDN (ten ostatni) - działa bezpiecznie i wielowątkowo. Do tego zastrzeżeń nie mam. Ale nie jest optymalny - używa lock, kiedy można się bez niego obejść i nie opóźnia tworzenia instancji (wydajność!). Nie pozwala na inicjalizację singletona parametrami przed odwołaniem się do instancji (brak statycznego konstruktora, ergo JIT tworzy instancję w dowolnym momencie przed jej wykorzystaniem).


    Poza tym, kod który przedstawiłem jest bardziej elegancki. Dla mnie to ważne.

    ReplyDelete
  5. Wzorzec Singleton jest z założenia jednowątkowy, więc taka implementacja nie jest błędem. Sposobów na zapewnienie wielowątkowości jest wiele i nie należy to do "specyfikacji" wzorca Singleton.

    Sposób zaprezentowany w MSDN jest powszechnie używanym "blokowaniem dwufazowym" i sprawdza się.

    ReplyDelete
  6. ...aż mi się przypomina dyskusja z ostatniego spotkania, jak to ludzie studiujący pewien kierunek stopniowo przejmują coraz więcej jego cech... Jesteś pewny, Skolimo, że nie masz już monitora ciekłokrystalicznego zamiast twarzy?... Takie niewinne pytanie...

    ReplyDelete
  7. i ktoś tu ostatnio wspominał o zaśmiecaniu swoich wypowiedzi różnorakimi znaczkami ;P

    ReplyDelete