xUnit vs Event – jak go przetestować?

Rozpoczynam kolejny projekt, który ma przynieść mi chwałę, sławę i pieniądze. Piszę go przy wykorzystaniu TDD (jak zwykle angielska wiki ma więcej do powiedzenia) Wszystko szło ładnie do momentu gdy nie natrafiłem na test w którym chciałem sprawdzić czy klasa którą testuję wywoła event. No bo jak sprawdzić teraz coś co wykona się później?
Na szczęście wujek google zna programistów, którzy znają odpowiedź na takie i inne pytania, w związku z czym szybko znalazłem odpowiedź na swój problem.
Zamieszczę ja tutaj ze stosownymi komentarzami od siebie, plus kilka własnych uwag, które może ktoś uzna za wartościowe. Jest taki oto sobie kod:

  1. // arrange
  2. var cut = new AsyncImageServiceDownloader();
  3. var isp = new ImagesServiceProvider();
  4. const string ServiceAddress = “http://www.digart.pl/”;
  5. const string PageSuffix = “/przegladaj/nowe.html?p=”;
  6. isp.ServiceAddress = ServiceAddress;
  7. isp.PageAddressSuffix = PageSuffix;
  8. cut.AddProvider(isp);
  9. int downloaded = 0;
  10. int canceled = 0;
  11. int suspended = 0;
  12. int resumed = 0;
  13. const uint Requested = 30;
  14. ManualResetEvent synvEvent = new ManualResetEvent(false);
  15.  
  16. // act
  17. cut.AddProvider(isp);
  18. cut.PageDownloaded += (s, e) => { downloaded++; };
  19. cut.PageDownloadCanceled += (s, e) => { canceled++; };
  20. cut.PageDownloadSuspended += (s, e) => { suspended++; };
  21. cut.PageDownloadResumed += (s, e) => { resumed++; };
  22.  
  23. cut.DownloadPageAsync(0, 0, Requested);
  24. for (uint i = 0; i < Requested; ++i)
  25. {
  26.     cut.SuspendDownload(0, i);
  27. }
  28.  
  29. synvEvent.WaitOne(1000);
  30. for (uint i = 0; i < suspended; ++i)
  31. {
  32.     cut.ResumeDownload(0, i);
  33. }
  34.             
  35. synvEvent.WaitOne(15 * 1000);
  36. // assert
  37. Assert.That(downloaded, Is.EqualTo(Requested));
  38.  

Widzimy podział na trzy AAA (nie znalazłem polskiego).
Na początku przygotowuje środowisko, które będę chciał sprawdzić; cut (class under test) to właściwy obiekt, który chce sprawdzić, isp służy do poprawnego zainicjalizowania obiektu cut. Ważny obiekt ManualResetEvent jest deklarowany i tworzony w linii 14 posłuży mi do synchronizacji i czekania na eventy. Linie od 18 do 21 to podpięcie się pod eventy, którymi klasa może rzucić. Choć w tym przypadku interesują mnie tylko dwa: suspended oraz downloaded. Przy użyciu wyrażeń lambda podpinam się pod eventy, a jedyną rzecz jaką robię to zliczam ich wywołania. W tym teście nie interesuje mnie zawartość argumentów.
Skoro wszystko mam już gotowe do działania to ogień! Linia 23 wywołuje asynchroniczne zaciąganie 30 elementów (requested=30). Chwilę potem do akcja wkracza pętla z niecierpiącym zwłoki poleceniem SuspendDownload następnie daję chwilę testowanemu obiektowi na wykonanie kodu odpowiedzialnego za wywołanie odpowiednich eventów. Wszystko dzięki wywołaniu ManualResetEvent.WaitOne(1000) – specjalnie podany jest timeout, ponieważ nigdzie nie będzie zmieniony jego stan. Czas się kończy, testy wykonują się dalej. Kolejne rozkazy; ResumeDownload tyle razy ile razy zdążyłem go zawiesić. I kolejny odpoczynek z timeout 15 sekund, co pozwala spokojnie zakończyć się wszystkim operacjom ściągnięcia.
Na sam koniec sprawdzam czy rzeczywiście udało się ściągnąć tyle ile początkowo żądałem. Voila! To tyle, proste prawda?

Moje dwie uwagi:
– Pamiętaj że w obsłudze eventa możesz samodzielnie zmienić stan ManualEventReset, jeśli z tego skorzystasz nie zapomnij jednak dodać timeout do WaitOne w przeciwnym wypadku, w razie niepowodzenia testów ManualResetEvent zawiesi działanie testów.
– Zastanów się czego dokładnie oczekujesz od twojego cut. Jeżeli cut wywoła PageDownloaded trzy razy to od razu będziesz szczęśliwy? A może chcesz sprawdzić czy cut wywoła PageDownloaded tylko trzy razy, a żadnych innych eventów ani razu.

Jakieś uwagi? Inne sposoby? Literówka?
Wiesz jak mnie znaleźć.
JS

Palcem po mapie – CultureInfo w natarciu

Przygotowania do 70-536 idą sobie idą. Miałem ostatni mały sparing z CultureInfo, zatem trzeba się podzielić wiedzą, którą wyniosłem z tej krótkiej walki.
CultureInfo zawiera wszystkie informacje, których będziemy potrzebować, żeby aplikacja była poprawnie językowa (oczywiście trzeba znać jeszcze język obcy). Zaczynając od tego po której stronie kartki zaczynamy pisać, poprzez znak oddzielający ułamek od części całkowitej, powoli sunąc przez nazwę kalendarza, który jest używany i wreszcie kończąc na znaku, którym oznacza się nieskończoność. To tylko część z informacji, które posiada ta klasa.
Z CultureInfo można skorzystać poprzez podanie go jako parametr IFormatProvider (ToString, string.Format, StringBuilder.AppendFormat) lub poprzez ustawienie go w aktualnym wątku jako aktualną kulturę metodą Thread.CurrentThread.CurrentUICulture = myNewCultureInfo;
Kawałek kodu, dzięki któremu można się bliżej poznać z kulturami z całego świata. Wszystko dzięki wbudowanemu wsparciu dla multi-kuli w windows.

  1. private void Form1_Load(object sender, EventArgs e)
  2. {
  3.     foreach (var ci in CultureInfo.GetCultures(CultureTypes.InstalledWin32Cultures))
  4.     {
  5.         this._avaiableCultures.Items.Add(ci.Name + “, “+ci.EnglishName+”( “+ci.NativeName+” )”);
  6.     }
  7.     this._avaiableCultures.SelectedIndex = 0;
  8. }
  9.  
  10. private void _avaiableCultures_SelectedIndexChanged(object sender, EventArgs e)
  11. {
  12.     string culture = this._avaiableCultures.SelectedItem.ToString().Substring(0,
  13.                      this._avaiableCultures.
  14.                      SelectedItem.ToString().
  15.                      IndexOf(“,”));
  16.  
  17.     StringBuilder sb = new StringBuilder();
  18.     CultureInfo ci = new CultureInfo(culture);
  19.     sb.AppendLine(“CALENDAR:”);
  20.     sb.AppendLine(ci.DateTimeFormat.NativeCalendarName);
  21.     sb.AppendLine(“DAYS:”);
  22.     foreach (var dayName in ci.DateTimeFormat.DayNames)
  23.     {
  24.         sb.AppendLine(dayName);
  25.     }
  26.     sb.AppendLine(“MONTHS:”);
  27.     foreach (var monthName in ci.DateTimeFormat.MonthNames)
  28.     {
  29.         sb.AppendLine(monthName);
  30.     }
  31.  
  32.     sb.AppendFormat(“NUMBERS:nFractions go after: {0}n”, ci.NumberFormat.CurrencyDecimalSeparator);
  33.     sb.AppendFormat(“Currency symbol: {0}n”, ci.NumberFormat.CurrencySymbol);
  34.     sb.AppendFormat(“Infinite symbol: {0}n”, ci.NumberFormat.PositiveInfinitySymbol);
  35.     this._localizedInfo.Text = sb.ToString();
  36.    
  37.     //CultureInfo myNewCultureInfo = new CultureInfo(culture);
  38.     //Thread.CurrentThread.CurrentUICulture = myNewCultureInfo;
  39.  
  40.     this._moneyValue.Text = string.Format(ci, “{0:C}”, 12345.67);
  41.     this._currentTime.Text = DateTime.Now.ToString(ci);
  42.  
  43.     if( ci.TextInfo.IsRightToLeft )
  44.     {
  45.         this._currentTime.RightToLeft = RightToLeft.Yes;
  46.         this._avaiableCultures.RightToLeft = RightToLeft.Yes;
  47.         this._currentTime.RightToLeft = RightToLeft.Yes;
  48.         this._localizedInfo.RightToLeft = RightToLeft.Yes;
  49.         this._moneyValue.RightToLeft= RightToLeft.Yes;
  50.     }
  51.     else
  52.     {
  53.         this._currentTime.RightToLeft = RightToLeft.No;
  54.         this._avaiableCultures.RightToLeft = RightToLeft.No;
  55.         this._currentTime.RightToLeft = RightToLeft.No;
  56.         this._localizedInfo.RightToLeft = RightToLeft.No;
  57.         this._moneyValue.RightToLeft = RightToLeft.No;
  58.     }
  59. }

Komentarz:
@01 – Na załadowanie formy, odczytuje wszystkie wspierane kultury. Następnie wrzucam informacje o nich do combobox.
@12  – Po wybrania innej kultury, sprawdzam jej skróconą nazwę.
@18 – Tworzę nową oraz wypisuje na jej podstawie nazwę kalendarza (@20), wszystki dni(@21), wszystkie miesiące(@26). Na koniec podstawowe jednostki płatnicze(@32). Przed wyjściem sprawdzam, czy w danej kulturze pisze się od lewej czy od prawej (@43) i aktualizuje kontrolki.

A tak wygląda aplikacja w działaniu:

Linq oraz Entity Framework – podstawowe polecenia i przykłady

Muszę się przyznać, że nigdy nie przepadałem za bazami danych. Zawsze uważałem je za nudny temat i nie odczuwałem większej potrzeby zajmowania się nimi. Prawdę mówiąc podobne zdanie miałem o C#, a teraz piszę w C# i uczę się Entity Framework, a w dodatku swoje doświadczenia spisuje i czasem się nimi dzielę.
Dzisiaj trochę o LINQ i Entity Frameworki, będą to raczej suche przykłady jak korzystać z linq, plus moje bardzo osobiste komentarze do nich. Przykłady pochodzą z książki, którą czytam i jeszcze nie wiem czy mogę ją polecić. Zła nie jest, ale jest w niej spora ilość błędów, które potrafią zdenerwować. Nadal jednak ją czytam, więc nie jest aż tak źle.
Entity Framework 4 in action” – bo o niej mowa, jest tworem trzech panów:
– Stefano Mostarda, – Marco De Sanctis
– Daniele Bochicchio

Książka ukazała się nakładem wydawnictwa Manning. Można ją zamówić w polskich księgarniach w cenie od 150 do 200 polskich złotych.Nie będę podawać przykładów na najprostsze zapytanie linq, zacznę od tych ciut trudniejszych, przykłady będą bazować na tych z książki
Przykłady będą się opierać na następującej bazie danych:

Oraz na takim mapowaniu jej do EDM:

Do rzeczy, czyli to co developerzy lubią najbardziej kod:

  • Grupowanie wyników na podstawie więcej niż jednego klucza jest możliwe przy pomocy nowego typu anonimowego, który posłuży za klucz do grupowania:
    1. using (var context = new OrderITEntities())
    2. {
    3.     var result = from o in context.Orders
    4.                  group o by new { o.ShippingAddress.City, o.ShippingAddress.ZipCode };
    5.  
    6.     foreach (var key in result)
    7.     {
    8.         Trace.WriteLine(string.Format(“{0}-{1}”, key.Key.City, key.Key.ZipCode));
    9.         foreach (var item in key)
    10.         {
    11.             Trace.WriteLine(item.OrderId);
    12.         }
    13.     }
    14. }

    Komentarz:
    @04 – Wyniki są grupowane na podstawie klucza {City, ZipCode}
    @06 – Najpierw trzeba przejść się po wszystkich kluczach
    @10 – Teraz można odczytać wyniki zgrupowane dla danego klucza.
    Wynikiem takiej operacji jest:

    Boston-87329
    17
    Chicago-ZipCode
    18
    city-zipc
    21
    City-ZipCode
    24
    fzxcb-xcbc
    46
    Los Angeles-34243
    31
    Los Angeles-54564
    14
    Miami-24323
    15
    Miami-34631
    16
    New York-0000001
    34
    35
    36
    New York-7777
    49
    New York-98765
    29
    30
    Rome-00100
    22
    23
    sc-sz
    32
    sdf-sdf
    47
    Seattle-24332
    13
    Seattle-98765
    12
    v<-cxv 44 Washington-98765 11 xc-dzvcx 45
  • Gdy nie potrzebna jest cała tabela, można skorzystać z projekcji wyników na nowy anonimowy typ, wybierając tylko to czego aktualnie potrzebujemy. Projekcja na przykładzie grupowania
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = from o in context.Orders
    4.             group o by o.ShippingAddress.City
    5.             into oGroup
    6.             select new { CityName = oGroup.Key, Items = oGroup };
    7.     foreach (var key in r)
    8.     {
    9.         Trace.WriteLine(string.Format(“{0}: {1}”, key.CityName, key.Items.Count()));
    10.         foreach (var item in key.Items)
    11.         {
    12.             Trace.WriteLine(item.OrderId);
    13.         }
    14.     }
    15. }

    Komentarz:
    @05 – wynik grupowania będzie w oGroup
    @06 – projekcja na nowy anonimowy typ
    @08 – pierwsza iteracja po kluczach
    @11 – kolejna iteracja po zgrupowanych obiektach
    Wyniki operacji:

    Boston: 1
    17
    Chicago: 1
    18
    city: 2
    21
    24
    fzxcb: 1
    46
    Los Angeles: 2
    14
    31
    Miami: 2
    15
    16
    New York: 6
    29
    30
    34
    35
    36
    49
    Rome: 2
    22
    23
    sc: 1
    32
    sdf: 1
    47
    Seattle: 2
    12
    13
    v<: 1 44 Washington: 1 11 xc: 1 45
  • Wybranie krotek spełniających nasze wymagania, a następnie zgrupowanie według adresu wysyłki:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var result = from o in context.Orders
    4.                  group o by o.ShippingAddress.City
    5.                      into g
    6.                      where g.Count() > 1
    7.                  select g;
    8.  
    9.     foreach (var key in result)
    10.     {
    11.         Trace.WriteLine(string.Format(“{0}: {1}”, key.Key, key.Count()));
    12.         foreach (var order in key)
    13.         {
    14.             Trace.WriteLine(order.OrderId);
    15.         }
    16.     }
    17. }

    Komentarz
    @04 – Wyniki zostaną zgrupowane na podstawia miasta
    @06 – Interesują nas tylko takie krotki, które posiadają więcej niż jedno zamówienie
    Wynik operacji

    city: 2
    21
    24
    Los Angeles: 2
    14
    31
    Miami: 2
    15
    16
    New York: 6
    29
    30
    34
    35
    36
    49
    Rome: 2
    22
    23
    Seattle: 2
    12
    13
  • Sortowanie według zadanego klucza, wykonane przez silnik DB:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var result = from o in context.Orders
    4.                  orderby o.ShippingAddress.City
    5.                  select o;
    6.  
    7.     foreach (var order in result)
    8.     {
    9.         Trace.WriteLine(string.Format(“{0}-{1}”, order.ShippingAddress.City, order.OrderId));
    10.     }
    11. }

    Komentarze
    @04 – Kluczem sortujący jest miasto
    Wynik operacji:

    Boston-17
    Chicago-18
    city-21
    City-24
    fzxcb-46
    Los Angeles-31
    Los Angeles-14
    Miami-15
    Miami-16
    New York-29
    New York-30
    New York-34
    New York-35
    New York-36
    New York-49
    Rome-22
    Rome-23
    sc-32
    sdf-47
    Seattle-12
    Seattle-13
    v<-44 Washington-11 xc-45
  • Posortowanie wyników według dynamicznie obliczanej wartości, wybranie tylko części do wyniku przy pomocy projekcji:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var result = from o in context.Orders
    4.                  where o.OrderDetails.Count > 0
    5.                  orderby o.OrderDetails.Sum(d => d.Quantity * (d.UnitPrice – d.Discount))
    6.                  select new
    7.                      {
    8.                          o.OrderId,
    9.                          o.OrderDate,
    10.                          o.ShippingAddress,
    11.                          Total = o.OrderDetails.Sum(d => d.Quantity * (d.UnitPrice – d.Discount))
    12.                      };
    13.     foreach (var item in result)
    14.     {
    15.         Trace.WriteLine(item);
    16.     }
    17. }

    Komentarze:
    @04 – Ta linia odrzuca zamówienia, które nie posiadają żadnych dodatkowych informacji, DB z którą pracuje najwyraźnie pozwala na złożenie zamówienia i nie przypisanie do niego szczegółów
    @05 – Wynik ma być posortowany, wg. nowej cechy zamówienia. Jest ona obliczania dynamicznie
    @06 – Projekcja – do wyniku zostaje dołączona informacja obliczana dynamicznie
    Wynik operacji:

    { OrderId = 16, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 1,0000 }
    { OrderId = 15, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 2,0000 }
    { OrderId = 46, OrderDate = 2010-04-20 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 4,0000 }
    { OrderId = 47, OrderDate = 2010-04-20 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 4,0000 }
    { OrderId = 21, OrderDate = 2009-02-03 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 18,0000 }
    { OrderId = 18, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 48,0000 }
    { OrderId = 34, OrderDate = 2009-10-28 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 50,0000 }
    { OrderId = 35, OrderDate = 2009-10-28 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 50,0000 }
    { OrderId = 36, OrderDate = 2009-10-28 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 50,0000 }
    { OrderId = 22, OrderDate = 2009-02-03 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 50,0000 }
    { OrderId = 23, OrderDate = 2009-02-04 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 50,0000 }
    { OrderId = 49, OrderDate = 2010-04-20 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 56,0000 }
    { OrderId = 30, OrderDate = 2009-08-09 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 90,0000 }
    { OrderId = 31, OrderDate = 2009-08-09 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 100,0000 }
    { OrderId = 12, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 100,0000 }
    { OrderId = 29, OrderDate = 2009-08-09 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 135,0000 }
    { OrderId = 13, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 152,0000 }
    { OrderId = 32, OrderDate = 2009-08-10 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 201,0000 }
    { OrderId = 17, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 254,0000 }
    { OrderId = 11, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 255,0000 }
    { OrderId = 24, OrderDate = 2010-05-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 270,2000 }
    { OrderId = 14, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 556,0000 }
  • Sortować można nie tylko główną tabele wyników, ale także wszystkie właściwości do których posiada ona referencje. Jest to możliwe tylko dzięki projekcji wyniku:
    1. using (var context = new Model.OrderITEntities())
    2.             {
    3.                 var result = from o in context.Orders
    4.                              select new
    5.                                         {
    6.                                             o.OrderId,
    7.                                             o.OrderDate,
    8.                                             Details = o.OrderDetails.OrderBy(d => d.Quantity)
    9.                                         };
    10.                 foreach (var item in result)
    11.                 {
    12.                     Trace.WriteLine(“oid: ” + item.OrderId + “, date: ” + item.OrderDate);
    13.                     foreach (var d in item.Details)
    14.                     {
    15.                         Trace.WriteLine(string.Format(“q:{0}, id:{1}”, d.Quantity, d.OrderDetailId));
    16.                     }
    17.                 }
    18.             }

    Komentarz: @06,07 – Wynik ma zawierać wybrane przez nas pola @08 – Details ma zawierać szczegóły dotyczące zamówienia, dodatkowo mają one zostać posortowane według ilości.
    Wynik operacji:

    oid: 11, date: 2008-11-27 00:00:00
    q:5, id:22
    q:5, id:23
    oid: 12, date: 2008-11-27 00:00:00
    q:2, id:24
    oid: 13, date: 2008-11-27 00:00:00
    q:2, id:25
    q:3, id:26
    oid: 14, date: 2008-11-27 00:00:00
    q:2, id:27
    q:5, id:28
    q:8, id:29
    oid: 15, date: 2008-11-27 00:00:00
    q:2, id:30
    oid: 16, date: 2008-11-27 00:00:00
    q:1, id:31
    oid: 17, date: 2008-11-27 00:00:00
    q:2, id:32
    q:2, id:34
    q:5, id:33
    oid: 18, date: 2008-11-27 00:00:00
    q:4, id:35
    oid: 21, date: 2009-02-03 00:00:00
    q:1, id:36
    q:2, id:37
    oid: 22, date: 2009-02-03 00:00:00
    q:5, id:38
    oid: 23, date: 2009-02-04 00:00:00
    q:5, id:39
    oid: 24, date: 2010-05-27 00:00:00
    q:1, id:69
    q:3, id:40
    q:5, id:41
    oid: 29, date: 2009-08-09 00:00:00
    q:45, id:44
    oid: 30, date: 2009-08-09 00:00:00
    q:2, id:45
    oid: 31, date: 2009-08-09 00:00:00
    q:2, id:46
    oid: 32, date: 2009-08-10 00:00:00
    q:13, id:47
    q:45, id:48
    oid: 34, date: 2009-10-28 00:00:00
    q:5, id:50
    oid: 35, date: 2009-10-28 00:00:00
    q:5, id:52
    oid: 36, date: 2009-10-28 00:00:00
    q:5, id:54
    oid: 44, date: 2010-04-20 00:00:00
    oid: 45, date: 2010-04-20 00:00:00
    oid: 46, date: 2010-04-20 00:00:00
    q:2, id:63
    oid: 47, date: 2010-04-20 00:00:00
    q:2, id:64
    oid: 49, date: 2010-04-20 00:00:00
    q:3, id:66
    q:5, id:68
  • Zwykły JOIN jest nudny 🙂 od razu przykład dla tej operacji, na 2 polach, które posłużą za podstawe łączenia tabel:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var result = from o in context.Orders
    4.                  join c in context.Companies.OfType<Customer>()
    5.                      on new { o.ShippingAddress.City, o.Customer.CompanyId }
    6.                      equals new { c.ShippingAddress.City, c.CompanyId }
    7.                  select o;
    8.  
    9.     foreach (var order in result)
    10.     {
    11.         Trace.WriteLine(string.Format(“oid:{0} date:{1} customer:{2} address:{3}”, order.OrderId,
    12.                         order.OrderDate, order.CustomerId,
    13.                         order.ShippingAddress.Address));
    14.     }
    15. }

    Komentarze
    @04 – Połączenie korzystać z klasy Order oraz Customer. Customer dziedziczy po Companies, trzeba jasno zaznaczyć, że z Companies interesują nas tylko Customer.
    @05 – Połączenia bazować będzie na nowym typie anonimowym, składającym się z City oraz ProductId
    Wynik operacji:

    oid:29 date:2009-08-09 00:00:00 customer:1 address:7th Avenue
    oid:30 date:2009-08-09 00:00:00 customer:1 address:7th Avenue
    oid:32 date:2009-08-10 00:00:00 customer:2 address:sa
    oid:34 date:2009-10-28 00:00:00 customer:1 address:2th street
    oid:35 date:2009-10-28 00:00:00 customer:1 address:2th street
    oid:36 date:2009-10-28 00:00:00 customer:1 address:2th street
  • Chciałem napisać też przykład dla OUTER JOIN, ale coś nie do końca go rozumiem. Nie chce pisać strasznych głupot. Wszystko rozbija się o użycie metody DefaultIfEmpty, nie będę niepotrzebnie wprowadzał fermentu i chaosu. Doczytam i zaktualizuje temat – obiecuje 🙂
  • W przykładach przyjęto metody TPC (Table Per Class) dla obiektów product, show oraz shirt. Przekłada się to na odpowiednie mapowanie w EF, klasy shoe oraz shirt dziedziczą po klasie product. Oczywiście tylko w C#, w sql jest to rozwiązane przy pomocy kluczy obcych. Pisząc zapytanie, które ma pobrać informację o jednym z wybranych produktów, korzystamy z dwóch tabel w sql; product oraz shoe/shirt. Linq nie jest w stanie tego samodzielnie wywnioskować, w związku z czymy należy mu w tym lekko pomóc:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var result = from p in context.Products.OfType<Shoe>()
    4.                  select p;
    5.     foreach (var product in result)
    6.     {
    7.         Trace.WriteLine(product.GetType().ToString());
    8.     }
    9. }

    Komentarz
    @03 – wybierz tylko obiekty, który są typu Shoe (będą zwracane poprzez proxy, ale dla użytkownika są one przezroczyste i korzysta się jak z normalnej klasy)
    Wynik operacji:

    System.Data.Entity.DynamicProxies.Shoe_7AA4169E6949EE79A10F953F29A44C8C07E207BD569BE65D49A2BA0AAA0A98EB
    System.Data.Entity.DynamicProxies.Shoe_7AA4169E6949EE79A10F953F29A44C8C07E207BD569BE65D49A2BA0AAA0A98EB

    Istnieje także alternatywna metoda, jest ona mniej wydajna, spełnia jednak swoje zadanie:

    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var result = from p in context.Products
    4.                  where p is Shirt
    5.                  select p;
    6.     foreach (var product in result)
    7.     {
    8.         Trace.WriteLine(product.GetType().ToString());
    9.     }
    10. }

    Komentarz
    @03 – Wybierz wszystkie obiekty Product
    @04 – Odfiltruj ze wszystkich, tylko te spełniające założenie.
    Wynik (jest taki sam jak poprzednio, znowu widać klasę proxy, jednak tym razem są to Shirt):

    System.Data.Entity.DynamicProxies.Shirt_9781A3B17068763ADD1262E9DF89ABE0B9179FD9113AA7C50B50B8F1AEE66B55
    System.Data.Entity.DynamicProxies.Shirt_9781A3B17068763ADD1262E9DF89ABE0B9179FD9113AA7C50B50B8F1AEE66B55
    System.Data.Entity.DynamicProxies.Shirt_9781A3B17068763ADD1262E9DF89ABE0B9179FD9113AA7C50B50B8F1AEE66B55
    System.Data.Entity.DynamicProxies.Shirt_9781A3B17068763ADD1262E9DF89ABE0B9179FD9113AA7C50B50B8F1AEE66B55
    System.Data.Entity.DynamicProxies.Shirt_9781A3B17068763ADD1262E9DF89ABE0B9179FD9113AA7C50B50B8F1AEE66B55
  • Może zaistnieć także sytuacja gdy potrzeba będzie znaleźć produkty danego typu, ale zwrócić jest w postaci kolekcji obiektów klasy podstawowej, na to także znajdą się dwa sposoby:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = from p in context.Products
    4.             where (p as Shoe).Sport == “Basket”
    5.             select p;
    6.  
    7.     foreach (var product in r)
    8.     {
    9.         Trace.WriteLine(string.Format(“{0}: {1}”, product.Name, product.Description));
    10.     }
    11. }

    Komentarz
    @03 – Wybierz wszystko
    @04 – Rzutuj na Shoe i spradź czy pole Sport jest równe Basket
    Wynik:

    Basket shoes: Basket shoes

    Wersja alternatywna:

    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = (from p in context.Products.OfType<Shoe>()
    4.              where p.Sport == “Basket”
    5.              select p).Cast<Product>();
    6.  
    7.     foreach (var product in r)
    8.     {
    9.         Trace.WriteLine(string.Format(“{0}: {1}”, product.Name, product.Description));
    10.     }
    11. }

    Komentarz
    @03 – Wybierz tylko Shoe
    @04 – Sprawdź warunek
    @05 – Wynik rzutuj na klasą bazową Product
    Wynik:

    Basket shoes: Basket shoes

Doszliśmy do końca zapytań i selekcji. W moich notatkach mam jeszcze małe co nie co na temat funkcji i ich wykorzystania. Dzielą się one na dwa rodzaje, te dostarczone przez EF (funkcje kanoniczne) oraz te dostarczone przez silnik bazodanowy.
Funkcje z EF:
+ działać będą z każdą bazą, która jest wspierana przez EF
– działać będą po stronie klienta
Funkcje z silnika
+ działają po stronie serwera
– silnie uzależniają od implementacji na którą się zdecydowaliśmy
Dwa przykłady:

  • Funkcja działająca w oparciu o EF
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = from o in context.Orders
    4.             where EntityFunctions.DiffDays(o.OrderDate, o.ActualShippingDate) > 5
    5.             select o;
    6.  
    7.     foreach (var order in r)
    8.     {
    9.         Trace.WriteLine(string.Format(“OrderId: {0} missing the date”, order.OrderId));
    10.     }
    11. }

    Skromy komentarz
    @04 – Jasno sprecyzowane jest, że sprawdzamy różnicę w dniach.
    Wynik operacji:

    OrderId: 11 missing the date
    OrderId: 49 missing the date
  • Identyczna funkcja, ale działająca w oparciu o DB
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = from o in context.Orders
    4.             where SqlFunctions.DateDiff(“d”, o.OrderDate, o.ActualShippingDate) > 5
    5.             select o;
    6.     foreach (var order in r)
    7.     {
    8.         Trace.WriteLine(string.Format(“OrderId: {0} missing the date”, order.OrderId));
    9.     }
    10. }

    Skromy komentarz
    @04 – Należy dodać informację o którą część daty
    Wynik:

    OrderId: 11 missing the date
    OrderId: 49 missing the date

I ostatnie czym chciałem się podzielić, to możliwość wykonania własnej kwerendy. Bez pomocy ze strony LINQ. Może się zdarzyć, że zapytanie stworzone przez LINQ będzie zbyt wolne, a samodzielnie będziemy w stanie napisać je w prostszy i bardziej wydajny sposób. Dodatkowo możliwe jest także dynamicznie zdefiniowanie parametów zapytania. Są dwa podejścia, pierwsze prostsze drugie odrobinę bardziej skomplikowane. Należy pamiętać, że nie wolno ich łączyć. Tak samo jak nie wolno kąpać gremlinów i karmić ich po północy .

  • Pierwszy przykład, to prosta kwerenda, bez szału:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = context.ExecuteStoreQuery<OrderDetailProjection>;
    4.             (“SELECT quantity, unitprice, discount FROM OrderDetail”);
    5.     foreach (var odp in r)
    6.     {
    7.         Trace.WriteLine(string.Format(“quantity:{0},discount:{1},unitprice:{2}”, odp.Quantity, odp.Discount,
    8.                         odp.UnitPrice));
    9.     }
    10. }

    Wynik:

    quantity:5,discount:0,0000,unitprice:1,0000
    quantity:5,discount:0,0000,unitprice:50,0000
    quantity:2,discount:0,0000,unitprice:50,0000
    quantity:2,discount:0,0000,unitprice:1,0000
    quantity:3,discount:0,0000,unitprice:50,0000
    quantity:2,discount:0,0000,unitprice:1,0000
    quantity:5,discount:0,0000,unitprice:50,0000
    quantity:8,discount:12,0000,unitprice:50,0000
    quantity:2,discount:0,0000,unitprice:1,0000
    quantity:1,discount:0,0000,unitprice:1,0000
    quantity:2,discount:0,0000,unitprice:1,0000
    quantity:5,discount:0,0000,unitprice:50,0000
    quantity:2,discount:2,0000,unitprice:3,0000
    quantity:4,discount:0,0000,unitprice:12,0000
    quantity:1,discount:0,0000,unitprice:10,0000
    quantity:2,discount:0,0000,unitprice:4,0000
    quantity:5,discount:0,0000,unitprice:10,0000
    quantity:5,discount:0,0000,unitprice:10,0000
    quantity:3,discount:0,0000,unitprice:3,4000
    quantity:5,discount:0,0000,unitprice:50,0000
    quantity:45,discount:0,0000,unitprice:3,0000
    quantity:2,discount:0,0000,unitprice:45,0000
    quantity:2,discount:0,0000,unitprice:50,0000
    quantity:13,discount:0,0000,unitprice:12,0000
    quantity:45,discount:0,0000,unitprice:1,0000
    quantity:5,discount:0,0000,unitprice:10,0000
    quantity:5,discount:0,0000,unitprice:10,0000
    quantity:5,discount:0,0000,unitprice:10,0000
    quantity:2,discount:1,0000,unitprice:3,0000
    quantity:2,discount:1,0000,unitprice:3,0000
    quantity:3,discount:0,0000,unitprice:2,0000
    quantity:5,discount:0,0000,unitprice:10,0000
    quantity:1,discount:0,0000,unitprice:10,0000
  • Teraz kwerenda z możliwością parametryzacji zapytania, wersja prostsza dynamicznego dodawania wartości dla zapytania. Jest identyczna jak w wywołaniu WriteLine(…):
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = context.ExecuteStoreQuery<string>
    4.             (“SELECT Name FROM company WHERE ShippingCity={0} AND billingcity={1}”, “New York”, “paris”);
    5.  
    6.     foreach (var name in r)
    7.     {
    8.         Trace.WriteLine(name);
    9.     }
    10. }

    Wynik:

    Billl Clay
  • Oraz wersja ciut trudniejsza, z wykorzystaniem paramentów ściślej związanych z bazą danych:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var a = new SqlParameter(“p0”, DbType.String) { Value = “New York” };
    4.     var b = new SqlParameter(“p1”, DbType.String) { Value = “Paris” };
    5.     var r = context.ExecuteStoreQuery<string>
    6.            (“SELECT name FROM company WHERE shippingcity= @p0 AND billingcity= @p1”, b, a);
    7.     foreach (var name in r)
    8.     {
    9.         Trace.WriteLine(name);
    10.     }
    11. }

    Wynik:

    Billl Clay

To na razie tyle. Myślę, że wyskrobię jeszcze kilka wpisów dotyczących DB i EF. Przypominam także, że moje wpisy nie są idealne i mogą zawierać błędy. Także nie trzymałbym się tak bardzo kurczowo tego co napisałem. Najlepiej będzie, jeśli sami spróbujecie czy to co napisałem działa oraz popróbujecie zmodyfikować kod, który jest tutaj przedstawiony.
Jak zawsze chętnie przyjmę na klatę wszystkie komentarze, którymi tak chętnie się ze mną dzielicie 😉
Jarek

Wzorce: Odwiedzający / Wizytator

Tak mnie wzięło na wzorce projektowe ostatnimi czasy, dzisiaj coś o o odwiedzającym (wizytatorze). Polska Wikipedia mówi o nim tak odwiedzający.
A teraz moimi słowami:
Wzorzec umożliwia przejście po strukturze danych, oraz zebranie jakichś informacji. Gdy zaistnieje potrzeba zaimplementowania nowej funkcjonalności, gdzie pobieranie danych jest zaimplementowane tak samo, ale rodzaj danych będzie się różnić, problem ten rozwiąże się tworząc nową klasę “odwiedzającą” strukturę danych, która zbierze nowe informacje.
Gdyby zastosować taki wzorzec np. do parsowania XML, pierwszy odwiedzający zbiera informacje o wszystkich linkach, które znajdują się w przeglądanym XML. Inna implementacja zlicza po prostu, wszystkie elementy, czy atrybuty takiego XML. W każdym przypadku, następuje przejście po całej strukturze, różnica jest natomiast w wyniku takiej operacji. Na tym właśnie polega “odseparowanie algorytmu od struktury obiektowej na której się operuje”.


Przykładowy kod:

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3.  
  4. namespace Visitor
  5. {
  6.     class SimpleExample
  7.     {
  8.         internal interface IElement
  9.         {
  10.             void Accept(IVisitor aVisitor);
  11.         }
  12.  
  13.         internal interface IVisitor
  14.         {
  15.             void Visit(IElement aElement);
  16.         }
  17.  
  18.         public class ElementClassA : IElement
  19.         {
  20.             public void Accept(IVisitor aVisitor)
  21.             {
  22.                 aVisitor.Visit(this);
  23.             }
  24.         }
  25.         public class ElementClassB : IElement
  26.         {
  27.             public void Accept(IVisitor aVisitor)
  28.             {
  29.                 aVisitor.Visit(this);
  30.             }
  31.         }
  32.  
  33.         public class ELementClassC : ElementClassB
  34.         {
  35.             new public void Accept(IVisitor aVisitor)
  36.             {
  37.                 aVisitor.Visit(this);
  38.             }
  39.         }
  40.  
  41.         public class RootElement : IElement
  42.         {
  43.             private ElementClassA _elementA;
  44.             private List<ElementClassB> _elementsBList;
  45.             private static ELementClassC _elementc = new ELementClassC();
  46.             public void Accept(IVisitor aVisitor)
  47.             {
  48.                 aVisitor.Visit(this);
  49.                 _elementA.Accept(aVisitor);
  50.                 foreach (var elementClassB in _elementsBList)
  51.                 {
  52.                     elementClassB.Accept(aVisitor);
  53.                 }
  54.                 RootElement._elementc.Accept(aVisitor);
  55.             }
  56.  
  57.             public RootElement()
  58.             {
  59.                 _elementA = new ElementClassA();
  60.                 _elementsBList = new List<ElementClassB>();
  61.                 for (int i = 0; i < 10; ++i)
  62.                 {
  63.                     _elementsBList.Add(new ElementClassB());
  64.                 }
  65.  
  66.             }
  67.         }
  68.  
  69.         public class VisitorToString : IVisitor
  70.         {
  71.             public int Count { get; set; }
  72.             public VisitorToString()
  73.             {
  74.                 Count = 0;
  75.             }
  76.  
  77.             public void Visit(IElement aElement)
  78.             {
  79.                 string s = string.Format(“Visited class {0}”, aElement.ToString());
  80.                 Count += s.Length;
  81.                 Console.WriteLine(s);
  82.             }
  83.         }
  84.  
  85.         public class VisitorHashString : IVisitor
  86.         {
  87.             public int Count { get; set; }
  88.             public VisitorHashString()
  89.             {
  90.                 Count = 0;
  91.             }
  92.             public void Visit(IElement aElement)
  93.             {
  94.                 string s = string.Format(“Visited class {0}, hash code {1}”, aElement.ToString(), aElement.GetHashCode());
  95.                 Count++;
  96.                 Console.WriteLine(s);
  97.             }
  98.         }
  99.         
  100.         public static void Main()
  101.         {
  102.             RootElement root = new RootElement();
  103.             VisitorToString vstring = new VisitorToString();
  104.             VisitorHashString vhstring = new VisitorHashString();
  105.             root.Accept(vstring);
  106.             Console.WriteLine(“Total written {0}”,vstring.Count);
  107.             root.Accept(vhstring);
  108.             Console.WriteLine(“Total written {0}”,vhstring.Count);
  109.         }
  110.     }
  111. }

W przykładzie jest dwóch odwiedzających. Jeden zlicza wszystkie literki, które zostały wykorzystane do wyświetlenia informacji o odwiedzanych elementach. Drugi zbiera informację o ilości odwiedzonych elementów. Oba wypisują jakieś tam informacje o odwiedzonych elementach, można to zignorować.
W przykładnie, klasy dziedziczące po IElement nie wykonują zbyt wiele poza akceptowaniem odwiedzającego, czy wysłaniem go do wszystkich swoich pól. IElement nie definiuje żadnej funkcjonalności, z której skorzystać mógłbym odwiedzający. Implementację odwiedzającego warto uodpornić na zmiany kolejności wywołać Accept (klasa agregująca), odwiedzający powinien poprawnie zadziałać bez względu na to czy przykładowy RootElement najpierw wyśle do odwiedzającego siebie, czy wcześniej przejdzie po wszystkich swoich polach i dopiero potem sama podda się odwiedzinom. Dlaczego warto tego przypilnować? Ponieważ nie zawsze mamy kontrolę na implementacją odwiedzanych elementów, np. zewnętrzna biblioteka. W nowszej wersji może się okazać, że zmieniono kolejność wywołania Accept ze względu na zwiększenie wydajności albo dodano nowe pola w odwiedzanej klasie.
Warto również zwrócić uwagę, że odwiedzający przyjmuje interfejs, nie będzie więc on w stanie wykonać innych operacji niż tam zdefiniowane. Można to rozwiązać na co najmniej dwa sposoby:
– Bardzo dobrze zdefiniować taki interfejs
– Dodać kolejne metody przeciążające metodę Accept, gdzie parametrem będą klasy na których odwiedzeniu nam zależy

Poniżej rozwiązanie drugie, troszkę rozbudowane:

Code Snippet
  1. using System;
  2.  
  3. namespace Visitor
  4. {
  5.     public interface IVisitor
  6.     {
  7.         void HandleVisit(IObject aObject);
  8.     }
  9.  
  10.     public interface IObject
  11.     {
  12.         void AcceptVisitor(IVisitor aVisitor);
  13.     }
  14.  
  15.     class GenericVisitor : IVisitor
  16.     {
  17.         void IVisitor.HandleVisit(IObject aObject)
  18.         {
  19.             throw new ArgumentException(
  20.                 string.Format(
  21.                     “Sorry but this visitor accepts only ConcreteObjectB types class. Please fix your code!. Error generated by instance of {0} class”,
  22.                     aObject));
  23.         }
  24.  
  25.         public void HandleVisit( ConcreteObjectB aObjectB)
  26.         {
  27.             Console.WriteLine(“Handle visit of class {0}”, aObjectB);
  28.         }
  29.     }
  30.  
  31.     class StronglyTypedVisitor : IVisitor
  32.     {
  33.         void IVisitor.HandleVisit(IObject aObject)
  34.         {
  35.             throw new ArgumentException(string.Format(“Just found new not properly handled visitor {0}- fix it”, aObject));
  36.         }
  37.  
  38.         public void HandleVisit(ConcreteObjectA aObject)
  39.         {
  40.             Console.WriteLine(“Visited by a strongly typed ObjectA instance”);
  41.         }
  42.  
  43.         public void HandleVisit(ConcreteObjectB aObject)
  44.         {
  45.             Console.WriteLine(“Visited by a strongly typed ObjectB instance”);
  46.         }
  47.     }
  48.  
  49.     class ConcreteObjectA : IObject
  50.     {
  51.         void IObject.AcceptVisitor(IVisitor aVisitor)
  52.         {
  53.             throw new ArgumentException(
  54.                 string.Format(
  55.                     “This object only accepts strongly typed objects. Fix your code. Class that generated exception {0}”,
  56.                     aVisitor));
  57.         }
  58.  
  59.         public void AcceptVisitor(StronglyTypedVisitor aVisitor)
  60.         {
  61.             aVisitor.HandleVisit(this);
  62.         }
  63.     }
  64.  
  65.     class StronglyTypedConcreteObject: IObject
  66.     {
  67.         public void AcceptVisitor(IVisitor aVisitor)
  68.         {
  69.             throw new ArgumentException(string.Format(“Consider using strongly typed methodsn. Please implement this method: public void AcceptVisitor({0} aVisitor)”, aVisitor));
  70.         }
  71.     }
  72.  
  73.     class ConcreteObjectB : IObject
  74.     {
  75.         void IObject.AcceptVisitor(IVisitor aVisitor)
  76.         {
  77.             throw new ArgumentException(
  78.                 string.Format(
  79.                     “ConcreteObjectB class accepts only StronglyTypedVisitor, please fix your code. Exception caused by instance of {0} class”,
  80.                     aVisitor));
  81.         }
  82.  
  83.         public void AcceptVisitor(StronglyTypedVisitor aVisitor)
  84.         {
  85.             aVisitor.HandleVisit(this);
  86.         }
  87.     }
  88.  
  89.     class Program
  90.     {
  91.         static void Main()
  92.         {
  93.             ConcreteObjectA a = new ConcreteObjectA();
  94.             ConcreteObjectB b = new ConcreteObjectB();
  95.             StronglyTypedVisitor sv = new StronglyTypedVisitor();
  96.  
  97.             //proper usage of code
  98.             a.AcceptVisitor(sv);
  99.             b.AcceptVisitor(sv);
  100.  
  101.             //now lets try to do some tricks
  102.             GenericVisitor gv = new GenericVisitor();
  103.  
  104.             try
  105.             {
  106.                 //a.AcceptVisitor(gv); this will generate compile error
  107.                 (a as IObject).AcceptVisitor(gv);       //oooo Im so great!
  108.             }
  109.             catch (ArgumentException ae)
  110.             {
  111.                 Console.WriteLine(“—————————————————————————————n{0}”,ae);
  112.             }
  113.  
  114.  
  115.             //example with synchronization lost!
  116.             try
  117.             {
  118.                 //Im sure it was agreed that the GeneralVisitor accepts ConcreteB, maybe its just a bug, I will try with the interface
  119.                 //b.AcceptVisitor(gv); //this will generate compile error
  120.                 (b as IObject).AcceptVisitor(gv);       //looks fine to me and the compilator, so it works!
  121.             }
  122.             catch (ArgumentException ae)
  123.             {
  124.                 Console.WriteLine(“—————————————————————————————n{0}”, ae);
  125.             }
  126.  
  127.         }
  128.     }
  129. }

C# oferuje możliwość zasłonięcia metody dziedziczonej po interfejsie, poprzez jej jawną implementacje (@17), oraz przeciążenie jej inną publiczną metodą, którą zadowoli kompilator (@25).
Wykorzystuje to do poinformowania użytkowników klas, że np. nie obsługuje ona innych typów klas, niż te dostępne publicznie (@19).
Skorzystałem z wyjątków, ze względu na czytelność kodu i przekazu dla użytkownika moich przykładowych klas. Innym sposobem jest np. zastosowanie asercji, które zadziałają jak należy, a w przypadku gdy aplikacja nie została dobrze przetestowana, nie wywalą wyjątku podczas oddawania aplikacji klientowi.
Niektórym może się nie spodobać takie przesłanianie metod, powiedzą że naruszona została zasada kontraktów i obiektowości. OK, ale! Zyskuje się na tym, wcześniejsze wykrywanie nie poprawnego korzystania z interfejsu. W dokumentacji można dodać odpowienie adnotacje, że dana implementacja odwiedzającego działa tylko z daną listą klas.
W części gdzie nie używam try/catch widać, że wszystko działa poprawnie, korzysta się z klas tak, jak osoba je pisząca sobie przewidziała. Nie potrzeba żadnych czarów, rzutowania etc. Wynikiem czego jest też poprawne działanie kodu. To akurat nie jest ciekawe.
Użytkownik decydując się na rzutowanie obiektów, korzysta w nie cywilizowany (@107, @120) sposób jest ciekawsza. Generowany jest czytelny komunikat i prośba o poprawienie kodu, oraz jedyny słuszny błąd w c# – wyjątek. I na koniec przykład z klasą GenericVisitor i ConcreteObjectB, widać jak można sobie rozsynchronizować kod. Co prawda przykład lekko naciągany, ale jednak.

Do czego się przydaje taki wzorzec, gdzie z niego skorzystać?
– Obiekty klas A i B można poddać walidacji, gdzie Wizytor będzie walidować ich poprawność. Warto też wtedy poprawniej nazwać poszczególne klasy.
– A i B zawierają jakieś zadania, które należy z nich wyciągnąć i zakolejkować, A pobiera informacje o zadaniach z sieciA a B pobiera z obserwowanego katalogu. Aplikacja tworzy odpowiednie klasy oczekujące na nowe zadanie, obiekty a lub b zgłaszają nadejście nowych zadań z odpowiednich źródeł, aplikacja przekazuje obiekt a lub b do obiektu zajmującego się zbieraniem takich zadań.
– A i B chcą uzyskać dostęp do strzeżonego obiektu, wcześniej muszą się zamedlować
u strażnika, aby sprawdzić czy mają odpowiednie uprawnienia dostępu
– Parsowanie skomplikowanych struktur

Bardziej życiowe porównanie, choć nie wiem czy najlepsze: organizujecie imprezę, wyklejacie plakaty: “Zapraszamy do nas na mega piwo”, przychodzą różne obiekty, klasa walidator przepuszcza część z nich, a innym grzecznie odmawia.

Cechy takiego wzorca:
– Trzeba napisać więcej kodu
– Może dojść do niesynchronizowania się klas odwiedzanych i odwiedzających, choć trzeba się o to porządnie postarać, dodałem go do przykładu (@120)
+ Czytelniejszy kod
+ Jasne określenie co jest wspierane przez klasy
+ Znika potrzeba rzutowania obiektów podczas ich obsługi
+ Jesteśmy zajebiści bo korzystamy ze wzorców projektowych

To tylko część z sytuacji, w których możliwe jest skorzystanie z tego wzorca. Jedynym ograniczeniem jest tutaj nasza wyobraźnia i zdrowy rozsądek.

JS

Początki raz jeszcze

Jest taka książka Accelerated C# 2010 którą postanowiłem przeczytać, aby utrwalić swoją wiedzę z C#. W polskiej księgarni można znaleźć za około 120 nowych polskich złotych.
Jest przyjaźnie napisana i tłumaczy w lekki (dla mnie) sposób C#. Jej dużym plusem jest to, że nie jest ona dla początkujących programistów, a raczej dla osób które posiadają wiedzę z C++/Java (czy innego języka) i teraz chcieli by się nauczyć szarpa. Dlaczego tak się zajarałem? Książki które czytałem wcześniej nie wspominały nic o destuktorach (ani o tym, że w zasadzie nie powinno się z nich korzystać) w C# czy konstruktorach statycznych.
Te statyczne konstruktory to dla mnie w ogóle faza, w javie też niby są, ja tam nie wiem, javą się nie interesowałem. Poza tym gostek (w sensie autor), pisze też trochę o ILDASM, jak to wygląda w środku i czego się nie powinno używać. Myślę sobie, że to dobra książka na powtórzenie podstawowych wiadomości o tym języku w wersji 4.0.
Przy okazji będę mógł, wreszcie uczciwie napisać z której książki nauczyłem się podstaw.

Słówka kluczowe:
Konstuktor statyczny
Destruktory
params (tego też nie znałem)

Uwagi do mojej wybrakowanej wiedzy, słownictwa i prostactwa proszę zamieszczać w komentarzach.
A korzystając z okazji zapraszam na:

Geeks on tour (spotkania trzech grup społecznościowych, związanych z technologiami Microsoft)
wrocnet (wrocławska grupa .net)