Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Tfs Spotlight – buduję własny CAB

Od kilku miesięcy nic tu nie pisałem (oczywiście poza poprzednim nieplanowanym wpisem). Jak łatwo się domyślić czas mi na to nie pozwalał. Z jednej strony natłok zadań w pracy (stabilizacja finalnej wersji – Comarch ALTUM ujrzał niedawno światło dzienne na tegorocznym CeBicie) a z drugiej strony projekt, który chciałbym opisać w tym tekście.

Pierwsze odcinki z serii Build your own CAB pojawiły się już dość dawno, a ponieważ Jeremy jest moim ulubionym bloggerem, to śledzę je od początku z zainteresowaniem i nie mogę się doczekać następnych (ciekaw jestem ilu z Was także czyta regularnie jego teksty). Osobiście jako zawodowy programista mam jeszcze niewielką praktykę w budowaniu złożonych aplikacji, ale czytając ciągle różne teksty, pojawiające się na blogach, moja głowa wypełnia się wiadomościami teoretycznymi, a po przeczytaniu wszystkich dostępnych dotąd artykułów z Serii zapragnąłem tę wiedzę wykorzystać w praktyce.

Pracę jako programista zacząłem właśnie od aplikacji budowanej w oparciu o Composite Application Block, pracuję z nią już ponad rok, a Seria Jeremy’ego pozwoliła mi z dystansu spojrzeć na rozwiązania zastosowane w CAB, lepiej je zrozumieć i wykorzystać. Ponadto chęć poszerzania swoich doświadczeń i umiejętności pchnęła mnie dalej. W Sieci można znaleźć pomysł, by Serię opatrzyć przykładami. Ten pomysł zachęcił mnie by wykorzystać zdobytą wiedzę w praktyce i wykonać jakiś projekt open source. Tak na marginesie trzeba zauważyć, że i tak najlepszym przykładem jest projekt Story Teler Jeremy’ego, z którego to pochodzi większość przykładów, które możemy zobaczyć w Serii.

Pomysł ten pojawił się w momencie, gdy szukałem trochę wolnego czasu by zająć się projektem dotyczącym Team Foundation Server Workitem Tracking, czyli po prostu zarządzaniem workitemami. Na co dzień pracuję w środowisku TFS i do tego celu używam programu Fissum. Program ten jest naprawdę sprytny i pozwala mi lepiej wykorzystać mój czas. Gdy pobieram wersję z repozytorium, bądź też kompiluję projekt, Team Explorer nie nadaje się do użytku, tak jak i całe Visual Studio. Są po prostu zablokowane. Inną sprawą jest to, że TE w ogóle jest toporny i wolny - czytałem, że w VS2008 ma być o niebo lepiej. Pożyjemy, zobaczymy. Program zewnętrzny, taki jak Fissum, powala mi w tym czasie zająć się moimi workitemami. W szczególności najczęściej korzystam z niego w fazie stabilizacji, w której zwykle mam do czynienia z błędami, które muszę poprawić. Dzięki niemu w czasie, gdy Visual Studio jest zajęte, mogę na boku analizować przychodzące błędy jak i weryfikować te już istniejące. To jest mój sposób na optymalizację czasu, a czas przecież trzeba szanować (szczególnie, gdy harmonogram ciśnie).

Można zapytać: skoro tak dobrze Ci się pracuje z Fissum, to czemu chcesz pisać swoje oprogramowanie? Przede wszystkim chodzi o naukę. Generalnie aplikacja ma być prosta, jednak z drugiej strony będzie na tyle skomplikowana, że większość tematów poruszanych w Serii znajdzie w niej zastosowanie - chociażby implementacja UI, komend, stanu menu, zakładek itd. Drugim celem było zapoznanie się z narzędziami, które od dawna czekają na półce - mam tu na myśli jakiś kontener IoC (w moim wypadku StructureMap) oraz bibliotekę do logowania (Logging Application Block już widziałem w akcji u Arka, więc wypadałoby z niego skorzystać). Trzecim powodem jest to, że projekt Fissum mimo że jest otwarty (open source), to jednak jest zamknięty. Program ten zawiera kilka rzeczy, które mi przeszkadzają. O jednej już pisałem. Ponadto mam kilka pomysłów jak można by było go usprawnić (generalnie chodzi o użyteczność a nie o funkcjonalność). Proponowałem Miiitchowi swoją pomoc jednak powiedział mi, że jest to jego prywatny projekt, na którym realizuje swoje pomysły dotyczące TFS. Trzeba to uszanować. Na początku modyfikowałem Fissum do swoich potrzeb, ale jest to uciążliwe do utrzymania, gdy Miiitch wydaje nowe wersje.

W niniejszym tekście chciałbym opisać kilka problemów, które napotkałem budując TfsSpotlight oraz znalezionych dzięki Serii rozwiązań. Jakoś tak wyszło, że zacząłem trochę od końca. W swojej Serii Jeremy nie poruszył jeszcze tematu Application Shell, a w momencie gdy pisałem pierwszą wersję aplikacji nie było też tekstu o Command Executor, a aby zbudować podstawę, trzeba było zacząć właśnie od tych elementów. Dlatego właśnie rozwiązania zawarte w TfsSpotlight dotyczące tych zagadnień są tylko i wyłącznie mojego pomysłu.

Oddzielenie logiki aplikacji od formatek

Po prezentacji Wojtka o MVC chyba każdy się ze mną zgodzi, że budując chociażby kalkulator należy oddzielić logikę dziedziny od sposobu prezentacji. Pierwszą i dla mnie najważniejszą zaletą takiego podejścia jest możliwość łatwego przetestowania logiki bez konieczności angażowania w ten proces komponentów graficznych. Nie napisałem tego wcześniej, ale w tym projekcie jednym z większych dla mnie wyzwań jest zadanie pisania testów jednostkowych dla jak największych części kodu. Co z tego wyjdzie – zobaczymy.

Od dłuższego czasu przy budowaniu formatek korzystam z pewnego wariantu wzorca Model-View-Presenter. Mówię tu wariantu, gdyż, tak jak już wspominał Wojtek na swojej prezentacji, nie ma jednej najlepszej implementacji wzorca MVP. W moim wykonaniu widok odpowiada wzorcowi PasiveView (Fowler, Jeremy), gdyż jest to postać najbardziej przyjazna testowaniu, bo zawiera minimalną ilość logiki. Prezenter jest główną jednostką dowodzącą, która zawiera wszelką logikę prezentacji i obsługi danego widoku - SupervisingController (Fowler, Jeremy). Z modelem natomiast bywa różnie. W prostych przypadkach są to bezpośrednio encje reprezentujące dane, na których dany widok pracuje. W przypadkach bardziej skomplikowanych preferuję oddzielną klasę modelu, która w szczególności zawiera logikę obsługi danych tj. wczytywanie, zapisywanie. Ma to szczególne znaczenie, gdy widok ma wiele źródeł danych (np. listy wyboru, które również trzeba zasilić danymi z bazy). W takich przypadkach model zajmuje się przygotowaniem wszystkich potrzebnych zestawów danych i odciąża tym prezentera. W niniejszym projekcie taka skomplikowana sytuacja jeszcze nie zaszła, gdyż biblioteki TFS dostarczają nam gotowych kontrolek do reprezentacji całych elementów, toteż zostałem zwolniony z konieczności implementowania ich własnoręcznie.

Wykorzystując MVP preferuję podejście bazujące na bezpośrednim odwoływaniu się widoku do prezentera. W porównaniu z podejściem opartym na zdarzeniach jest to podejście znacznie prostsze. Przede wszystkim dlatego, że prostsza jest nawigacja po kodzie. Mając bezpośrednie odwołania do metod możemy wykorzystać narzędzia nawigacyjne, jakie daje nam Visual Studio, i przemieszczać się z widoku do prezentera dwoma kliknięciami myszy. Sprawa się trochę komplikuje, jeżeli prezenter jest opisany interfejsem. Wtedy niestety VS sobie nie radzi i pokazuje nam implementację interfejsu, a nie kod konkretnego prezentera, czyli nie do końca tego, czego byśmy chcieli. Problemu tego nie mają użytkownicy ReSharepera, którego genialna funkcja Go to inheritor pozwala natychmiast przemieścić się do klasy implementującej dany interfejs. Poza tym implementacja zdarzeń wymaga od nas o wiele większego nakładu pracy. Należy przecież w widoku zaimplementować zdarzenia dla każdej możliwej do wykonania operacji. Następnie w prezenterze trzeba do tych wszystkich zdarzeń podpiąć odpowiednie metody. Jak dla mnie za dużo roboty.

Zarządzanie komendami

Pytanie jest proste: jak zarządzać komendami, które użytkownik może wykonywać? Wymagania zwykle są następujące:

  1. Komenda powinna być dostępna w wielu miejscach aplikacji: menu główne, menu kontekstowe, ikona na pasku narzędzi, skrót klawiszowy, wywołanie z kodu programu nawet w innym module.
  2. Zarządzanie stanem komendy – czy jest aktywna czy nie.
  3. Sposób uruchomienia komendy – synchroniczne czy asynchroniczne – w samej implementacji komendy chcielibyśmy abstrahować od sposobu jej uruchomienia.

Jak nie trudno się domyśleć bez spójnego mechanizmu definiowania i obsługi komend szybko zabrniemy w ślepy zaułek i rozwijanie aplikacji stanie się nieprzyjemne. Z resztą bez odpowiedniego mechanizmu trudno będzie w prosty sposób zarządzać choćby stanem poszczególnych komend. Z pomocą przychodzi nam bardzo prosty wzorzec – Komenda (Jeremy). Wzorzec ten wprowadza interfejs komendy – ICommand – dzięki któremu możemy wykonanie każdej komendy zunifikować do postaci wykonania jednej metody ICommand.Execute(). Osobiście nigdy nie wykorzystywałem jeszcze tego podejścia, znałem je jedynie z definicji. Pierwsze pytanie jakie mi się nasunęło to jak dana komenda ma poznać swój kontekst (czyli dane na których ma pracować)? Przecież metoda Execute nie przyjmuje żadnego parametru! Tutaj zrozumiałem, że aby dobrze zaimplementować ten wzorzec trzeba do tego odpowiednio nasz system przygotować.

Na przykład rozważmy interfejs programu, który opiera się na zakładkach. Z reguły będziemy mieli jeden pasek z przyciskami i jedno menu główne, gdzie umieścimy komendy dotyczące aktywnej zakładki. Aby móc dostarczyć tym komendom kontekstu należy wprowadzić jakiś sposób pobierania aktywnej zakładki, aby komenda mogła oddelegować do niej akcję. Podobnie należy pomyśleć o innych elementach powłoki, do których będziemy chcieli mieć dostęp. Wydzielenie odpowiednich serwisów pozwala szybko odpowiedzieć na pytanie „jak dana komenda ma uzyskać interesujące ją dane”.

Kontynuując zadanie, potrzebny jest nam teraz spójny sposób obsługi komend. Po pierwsze potrzebujemy jednolitego sposobu przypisywania komend do kontrolek, a po drugie jakiegoś mechanizmu pozwalającego te komendy uruchamiać. Zajmijmy się teraz pierwszym zadaniem, a drugie omówimy sobie trochę później.

Od jakiegoś już czasu obserwuję w eterze coraz częściej pojawiające się odwołania do tekstu Martina Fowlera o fluent interfaces. Co więcej, zauważyć można pojawiające się implementacje wykorzystujące ten sposób budowania interfejsów klas. Prawdę mówiąc, gdy po raz pierwszy przeczytałem ten artykuł, idea bardzo mi się spodobała, ale nie widziałem jeszcze konkretnego jej zastosowania. Dopiero później, używając Rhino Mocks zauważyłem, że przecież używam właśnie fluent interface! I rzeczywiście, do zadań konfiguracyjnych podejście to jest niezastąpione, a wynikowy kod jest niesamowicie czytelny. Idąc za przykładem Jeremy’ego konfigurację komend zaimplementowałem wykorzystując tę technikę. Z wyniku jestem bardzo zadowolony, gdyż powstał naprawdę bardzo elastyczny, a zarazem spójny mechanizm przypinania komend do interfejsu użytkownika. Sposób implementacji możecie zobaczyć w kodzie (plik ConfigureMenuExpression.cs). Poniżej przedstawię tylko wynik użycia tej klasy konfiguracyjnej.

Przykład [C#] 1. Przykład wykorzystania klasy konfiguracyjnej wykorzystującej fluent interface.

ConfigureMenuExpression
    .Execute(CommandsNames.ExitApplication)
    .Synchronous()
    .For(this.miExit)
    .For(this.tsbExit)
    .WithShortcut(Keys.F10)
    .Enable();

ConfigureMenuExpression
    .Execute(CommandsNames.SaveAllWorkItems)
    .For(this.tsbSaveAll)
    .WithShortcut(Keys.Control | Keys.Shift | Keys.S)
    .Disable();


Dzięki zastosowaniu takiego mechanizmu udało mi się zebrać całą logikę dotyczącą konfiguracji komend w jednym miejscu. Jak łatwo zauważyć spełniłem większość wymagań postawionych wcześniej. Po pierwsze daną komendę możemy przypisać do wielu elementów, możemy nadać skrót klawiszowy, możemy oznaczyć jako operację synchroniczną (domyślnie komendy uruchamiane są w trybie asynchronicznym) oraz możemy nadać komendzie początkowy stan. Wszystko w jednym miejscu, wszystko czytelne do granic możliwości. Kontrastując to z koniecznością odpalenia formatki w trybie projektowania, nawigowaniu po elementach menu i sprawdzaniu w panelu właściwości czy podpięta jest odpowiednia metoda i czy został dobrze zdefiniowany skrót klawiszowy widać jak wiele zalet ma przedstawione tu podejście.

Powłoka – application shell

Chciałem, aby moja aplikacja składała się z okna głównego (powłoki, ang. shell), które zawierać będzie podstawowe elementy takie jak menu, pasek narzędzi z ikonami, pasek statusu oraz kontener na zakładki. W odróżnieniu od Fissum chciałem, aby TfsSpotlight pracował cały czas w jednym oknie, a za pomocą zakładek pozwalał na otwieranie wielu elementów jednocześnie. Ponadto chciałem dać użytkownikowi bezpośredni dostęp do zapytań zdefiniowanych dla danego projektu, co zaowocowało powstaniem panelu bocznego.



W moim odczuciu zadaniem powłoki jest dostarczenie API pozwalającego na manipulację jej elementami. Dlatego właśnie wydzieliłem z niej kilka serwisów (zarządzanie menu – komendami, zarządzanie zakładkami, zarządzanie paskiem statusu oraz zarządzenie zakładkami). Zdefiniowałem spójne interfejsy opisujące te usługi i dzięki temu dowolny element systemu może mieć dostęp do elementów wspólnych.

Jak to wszystko ze sobą powiązać?

Każdy, kto choć trochę liznął wzorców projektowych GoF, do tego problemu podszedłby z Singletonem pod pachą. Implementując każdy z serwisów powłoki w postaci singletonu umożliwiamy innym elementom systemu łatwy dostęp do instancji tych serwisów. Niestety singleton ma jedną poważną wadę – bardzo, ale to bardzo mocno wiąże ze sobą klasy. Klasa, która odwołuje się do elementów statycznych innej klasy jest z nią bardzo mocno związana. Dlaczego nie chcemy takiego mocnego powiązania? Przecież miliony programistów na całym świecie używają singletonów z powodzeniem. Otóż tak mocne powiązania nie pozwalają efektywnie testować klas w izolacji, a przecież o to właśnie chodzi w pisaniu testów jednostkowych – żeby poszczególne klasy testować w izolacji. Zatem z mojego punktu widzenia i z punktu widzenia testów jednostkowych silngleton wypada blado. Na marginesie należy dodać, że istnieją narzędzia pozwalające testować takie sytuacje. Narzędziem takim jest TypeMock, które w środowisku praktyków TDD jest dość kontrowersyjne.

Z pomocą przychodzi nam zasada odwracania zależności (Dependency Inversion Principle) i narzędzia pozwalające tę zasadę wprowadzać w życie, czyli kontenery IoC (Inversion of Control). Osobiście w żadnym z moich prywatnych projektów nie korzystałem jeszcze z tego typu narzędzi. Naturalnym moim wyborem oczywiście jest StructureMap Jeremy’ego. W momencie, gdy piszę ten tekst, na horyzoncie jest już Unity ze stajni Microsoftu. David Hayden zrobił screencast wprowadzający w jego użycie.

Zasada odwracania zależności mówi, że:
  1. Moduły wysokiego poziomu nie powinny być zależne od modułów niższego poziomu. I jedne i drugie powinny być zależne od abstrakcji.
  2. Abstrakcje nie powinny być zależne od szczegółów. To szczegóły powinny być zależne od abstrakcji.
Klasa A zależy od klasy B, gdy klasa A wymaga obecności klasy B podczas kompilacji. Klasa A jest nazywana klientem natomiast klasa B – usługą. O zależnościach między klasami pisał już Stefan Jungmayr. Jungmayr wyróżnił dwa rodzaje zależności:
  1. Zależność od typu (ang. dependency on a type) oznacza, że egzemplarz usługi musi implementować pewien dobrze określony typ, który może być zdefiniowany za pomocą interfejsu, klasy abstrakcyjnej bądź konkretnej klasy.
  2. Zakodowana zależność (ang. hard-wired dependency) oznacza, że egzemplarz usługi musi być konkretnego typu. Zwykle odwołanie do konstruktora usługi lub elementu statycznego prowadzi do powstania zakodowanej zależności.

Aby lepiej zobaczyć o czym mówi ta zasada, przeanalizujmy przykład dwóch klas, które zaprojektowano bez jej uwzględnienia.

Przykład [C#] 2. Przykład dwóch klas zaprojektowanych bez uwzględnienia zasady odwracania zależności.

public class RefreshQueryCommand : ICommand
{
    public void Execute()
    {
        ITabPresenter presenter = TabsService.Instance.GetActiveTab();
        if (presenter != null)
        {
            presenter.HandleRefresh();
        }
    }
}

W przykładzie 2 mamy dwie klasy. Klasa RefreshQueryCommand reprezentuje komendę odświeżającą zakładkę. Druga klasa – TabsService – reprezentuje serwis obsługujący zakładki i została zaimplementowana w postaci singletonu. Klasa RefreshQueryCommand ma zakodowaną zależność do klasy TabsService. Jest to spowodowane odwołaniem się do statycznej właściwości tej klasy. Taka sytuacja skutecznie utrudnia nam przetestowanie komendy w izolacji. Testując ją chcielibyśmy sprawdzić dwa przypadki: jeżeli aktywna zakładka istnieje operacja powinna zostać do niej oddelegowana natomiast jeżeli nie to nic nie powinno zostać wykonane. Niestety bezpośrednie odwołanie do klasy TabsService utrudnia nam odpowiednie przygotowanie kontekstu testu.

Problemy te pomaga nam ominąć zasada odwracania zależności. Dzięki niej pozbędziemy się niechcianego odwołania do statycznej właściwości klasy TabsService. Zasada mówi, że tak moduły wysokiego poziomu jak i moduły niskiego poziomu powinny zależeć od abstrakcji. Spróbujmy zatem wprowadzić abstrakcję do przykładu 2. W module wyższego poziomu – klasie RefreshQueryCommand – chcemy abstrahować od klasy TabsService. Opiszmy zatem klasę TabsService interfejsem. Jego definicje przedstawiono w przykładzie 3.

Przykład [C#] 3. Wyodrębniony interfejs klasy TabsService.

public interface ITabsService
{
    /// <summary>Gets active tab</summary>
    /// <returns>A presenter of a currently active tab</returns>
    ITabPresenter GetActiveTab();
}


Mając abstrakcje spróbujemy teraz uniezależnić komendę od konkretnej realizacji serwisu. Chcemy uniknąć odwołania do statycznej właściwości klasy TabsService i pozwolić innej klasie podjąć decyzję o konkretnej realizacji. Do tego celu stworzymy konstruktor, który jako parametr będzie brał implementacje interfejsu ITabsService. Ulepszoną realizację klasy RefreshQueryCommand wykorzystującą klasę TabsService poprzez interfejs przedstawiono w przykładzie 4.

Przykład [C#] 4. Ulepszona realizacja klas RefreshQueryCommand.

public class RefreshQueryCommand : ICommand
{
    private ITabsService TabsService { get; set; }

    public RefreshQueryCommand(ITabsService tabsService)
    {
        this.TabsService = tabsService;
    }

    public void Execute()
    {
        ITabPresenter presenter = this.TabsService.GetActiveTab();
        if (presenter != null)
        {
            presenter.HandleRefresh();
        }
    }
}

Na czym polega odwrócenie zależności przedstawione w przykładzie 4? Zauważmy, że w obecnej sytuacji klasa RefreshQueryCommand nie zależy już od klasy TabsService, ale od interfejsu ITabsService. Odwrócenie zależności polega na tym, że teraz klasa TabsService również zależy od interfejsu ITabsService. Zatem zarówno klasy wyższego jak i niższego poziomu zależą od abstrakcji a nie od konkretnych realizacji.

Wśród narzędzi do zarządzania zależnościami możemy przebierać. Począwszy od wspomnianego StructureMap, czy Unity, po Spring.Net oraz Castle Windsor (w szczegóły wprowadzi Was Michał Harasimowicz na XXII spotkaniu wg.net – mam nadzieję, że pojawi się jakieś nagranie z tej sesji :). Ja swój projekt oparłem o pierwszy z nich. Zasada działania jest banalnie prosta. Najpierw musimy wrzucić do kontenera zabawki, aby potem pojawiły się automatycznie, gdy będziemy ich potrzebować. Większość z kontenerów obsługuje dwa typy wstrzykiwania implementacji: poprzez konstruktor oraz poprzez settery. Ja osobiście wolę pierwszy ze sposobów, ale to kwestia wyboru.

Zwykle, budując system luźno powiązany, opieramy się na interfejsach definiujących kontrakty. Tak też zrobiłem w moim projekcie. Każdy ze wspomnianych wcześniej serwisów powłoki opisany został interfejsem. Po zarejestrowaniu konkretnych implementacji tych interfejsów w kontenerze mogę bez ograniczeń z nich korzystać. Teraz, jeżeli dana klasa potrzebuje instancji danego serwisu, to do jej konstruktora dodaję parametr o typie będącym interfejsem tego serwisu. Zmienia się tylko sposób tworzenia instancji klas. Teraz zamiast używać słowa kluczowego new skorzystać należy z kontenera, aby ten przygotował na instancję żądanej klasy. Zadaniem kontenera jest znalezienie konstruktora i wypełnienie go wszystkimi wymaganymi parametrami. Parametry te są rozpoznawane na podstawie typów. W taki oto magiczny sposób zabawki automatycznie pojawiają się w piaskownicy.

Dzięki wykorzystaniu kontenera możemy budować klasy, które wszystkie swoje zależności otrzymują z zewnątrz. Oznacza to, że klasy te, same nie tworzą innych obiektów potrzebnych im do współpracy. Patrząc z punktu widzenia testów jednostek potencjalny zysk jest wielki. Teraz, aby daną klasę przetestować w izolacji, wystarczy, że utworzymy jej instancję z zależnościami będącymi obiektami zastępczymi (ang. mock objects). Dzięki temu testując możemy się skupić na kodzie tylko i wyłącznie testowanej klasy, a nie usług, od których testowana klasa zależy.

Pozbywając się singeltonów tracimy także kontrolę nad tym ile instancji danej klasy powstanie. A co jeżeli rzeczywiście chcieliśmy, aby dana klasa miała tylko jedną instancję? Otóż nic prostszego. Kontener daje nam możliwość określenia w jaki sposób instancja danej klasy ma być kontrolowana. Sposób wykonania tego z użyciem StructureMap opisał Jeremy na stronie projektu.

Uruchamianie komend - command runner

Nie wiem jak pozostałe kontenery, ale StructureMap ma bardzo fajną opcję rejestrowania instancji konkretnych klas pod pewną ustaloną nazwą. Później, chcąc otrzymać tę nazwaną instancję możemy zapytać kontener podając mu właśnie tę nazwę. Opcja ta jest o tyle sprytna, że pozwala w banalny sposób spełnić pozostałe wymagania dotyczące obsługi komend, o której pisałem wcześniej.

Jak można było zauważyć wcześniej przy konfiguracji komend, nigdzie nie podaliśmy ani klas, ani metod obsługujących konkretne komendy. Jedyne co widzieliśmy to stała w postaci CommandsNames.ExitApplicationCommand. Aby zbudować system elastyczny, który będzie spełniał wszystkie wymagania, nie mogliśmy podczas konfiguracji komend podać ich konkretnych implementacji chociażby dlatego, że implementacja danej komendy wcale nie musi znajdować się w klasie dostępnej podczas kompilacji. Dlatego właśnie do identyfikacji komend używamy stałych napisowych. Stałych tych używamy przy rejestracji implementacji komend w kontenerze, a dzięki temu znając nazwę komendy możemy później zapytać kontener o jej instancję.

Przykład [C#] 5. Realizacja klasy uruchamiającej komendy.

public class CommandRunner : ICommandRunner
{
    public void Run(string commandName)
    {
        Run(commandName, true);
    }
    public void Run(string commandName, bool runInAsync)
    {
        var command = ObjectFactory.GetNamedInstance<ICommand>(commandName);
        if (runInAsync)
        {
            ThreadPool.QueueUserWorkItem(delegate { command.Execute(); }, null);
        }
        else
        {
            command.Execute();
        }
    }
}

Dzięki opisaniu komend wspólnym interesem implementacja klasy CommandRunner staje się banalnie prosta. Ponadto klasa ta też została opisana intefjesem i zarejestrowana w kontenerze dzięki czemu nawet klasy z innych assembly mogą się do niej odwoływać i wykonywać komendy.

Ktoś z Was może zauważyć, że rzeczywiście staramy się tworzyć system luźno powiązany a jednocześnie korzystamy z kontenera, który dostarcza implementacji wymaganych typów za pomocą statycznej klasy ObjectFactory (sic!). Prawda jest taka, że niestety kontener nie może dostarczyć instancji samego siebie więc, akurat w tym przypadku nic nie możemy zrobić. Dlatego trzeba ograniczać miejsca, gdzie będziemy się bezpośrednio odwoływać do niego. Dobrą praktyką, pozwalającą łatwo testować takie odwołania, jest umieszczenie tego odwołania w metodzie wirtualnej, którą na czas testów będziemy mogli nadpisać i zwrócić z niej to, czego będziemy potrzebowali w kontekście testu.

Podsumowanie

Zachęcam wszystkich, którzy jeszcze nie czytali Serii, by w wolnej chwili to zrobili. Zachęcam w szczególności tych, którzy podobnie jak ja, pracują z CAB na co dzień. Nie chodzi oczywiście o to by każdy z nas budował własny CAB, ale by poznać techniki i koncepcje pozwalające pisać lepszy kod i lepiej projektować systemy.

Projekt, którego część tutaj opisałem, na pewno nie jest idealny, ale jego budowanie przynosi mi wiele satysfakcji. Na pewno w miarę wolnego czasu będę go rozwijał. Jeżeli komuś z Was się przyda to tym lepiej. Wszystkie uwagi są oczywiście mile widziane, tak do samej architektury, jak i funkcji programu.

Komentarze:

# re: Tfs Spotlight – buduję własny CAB

13 marca 2008 09:55 by arkadiusz.wasniewski

Bardzo dobry tekst. Czekam na następne!

W kwestii Jeremego - fakt, artykuły ma ponad przeciętne. Poleciłbym również blog autora RhinoMock czyli http://www.ayende.com/Blog.

Jeśli chodzi o przykłady 2, 3 i 4 to pokazujesz bardzo ładne wprowadzenie do wzorca most (Bridge Pattern), który jest bardzo pomocny zwłaszcza w przypadku łączenia klas implementujących CRUD (Create-Read-Update-Delete) z konkretnymi źródłami danych.

Powodzenia Arek

# re: Tfs Spotlight – buduję własny CAB

13 marca 2008 15:06 by nuwanda

To prawda. Nie zauważyłem tego wcześniej, ale rzeczywiście w wymienionych przez Ciebie przykładach mamy do czynienia z wzorcem Most. Masz świetne oko do wzorców Arku ;)

Co do Ayende to jest to jeden z najbardziej produktywnych bloggerów jakich znam.

# Biblioteka dostępu do TFS i testy jednostkowe

3 kwietnia 2008 12:57 by nblog

W poście Tfs Spotlight – buduję własny CAB wprowadzającym do mojego projektu TfsSpotlight wspomniałem,

# re: Tfs Spotlight – buduję własny CAB

22 listopada 2010 22:09 by HillaryWheeler

The <a href="http://bestfinance-blog.com/topics/home-loans">home loans</a> are useful for people, which want to start their own organization. By the way, that's comfortable to receive a car loan.

Komentarze anonimowe wyłączone