\

Školení Návrhové vzory, OOP a UML


 Wednesday, April 07, 2010
Pozvánka na kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1 – jaro 2010

Update: Kurz je zcela obsazen včetně náhradníků.

Rád bych Vás pozval na další běh kurzu Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1. Pokud se někdo z Vás (oprávněně) diví, proč tak pozdě a proč Vás nezvu i na kurz OOP 2, níže v tomto spotu nalezne odpovědi.

Veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1

Datum konání kurzu:  14. 6. – 16. 6. 2010

Místo konání:

Školící středisko Tutor

U Půjčovny 2
110 00 Praha 1

Po celý den máme k dispozici wifi připojení a samozřejmě také teplé a studené nápoje. V ceně kurzu jsou obědy v hotelu.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu
Výběr z ohlasů na kurz

Odpovědi na některé dotazy, které jsme obdrželi od dříve přihlášených zákazníků emailem.

Otázka: Proč se kurz nekoná na předchozím místě (v hotelu Villa), kde byli naši kolegové?

Odpověď: Po vyhodnocení ohlasů na kurz jsme zjistili, že účastníci nebyli  (stejně jako já :-)) příliš spokojeni s jídlem. Také jsme při domlouvání hotelu byli ujišťováni, kolik účastníků se může na kurz přihlásit  a jak lze pro ně uspořádat pohodlně prostory, a tyto informace se v průběhu kurzu jevily jako marketingové fráze až blafy velmi optimistickými nebo nepočítaly s průměrným Evropanem, ale standardním liliputem.:-) Stručně řečeno – poměr cena/výkon nebyl u čtyřhvězdičkového hotelu příliš dobrý. Jedinou výhodou hotelu Villa bylo parkování, nyní bude lepší na ”poslední míli” použít  MHD.

Otázka: Proč se koná kurz tak pozdě (oproti přechozím rokům) a proč se nekoná kurz OOP2.

Odpověď: Byl a jsem vytížen tento rok dalšími projekty a nalézt vhodné termíny bylo problematické.  Také jsme sháněli nové prostory, kde se bude kurz konat (viz předchozí odpověď). Termín kurzu OOP2 vycházel již na červenec a v době dovolených nemá smysl kurz pořádat. Při větším zájmu vyhlásím (co nejdříve) více termínů kurzu OOP2 na podzim.

 

Těším se na setkání na kurzu!


Share/Save/Bookmark Wednesday, April 07, 2010 12:48:36 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Analytické drobky | Kurzy UML a OOP | Návrhové vzory | UML


 Sunday, March 21, 2010
Výhody a nevýhody softwarových továren

Emailem jsem dostal zajímavou otázku, jaký je můj názor na softwarové továrny a kde vidím výhody a nevýhody softwarových továren. Odpověď nakonec publikuji i zde – už jen proto, že jsem si při jejím psaní uvědomil, že na továrnu kladu stejné nároky jako na kteroukoli další knihovnu v systému a že výběr softwarové továrny se u mě moc neliší od výběru třeba ORM Frameworku. Nejde o taxativní výčet výhod a nevýhod, ale spíš o volně nahozená témata, která mě za 20 minut psaní příspěvku napadla.

SW továrny jsou jen pokračováním trendu, že nemá smysl vynalézat znovu kolo ani v případě, že by bylo o dvě setiny procenta krásnější než to staré, které je již navržené a hotové. SW továrna říká – namísto opakování chyb svých předchůdců použijte ověřené zkušenosti, osvědčené praktiky, doporučené postupy a již otestovaný kód. Namísto obecného návrhového vzoru nebo vágně popsaného doporučovaného postupu máte k dispozici osvědčenou knihovnu, základní průvodce a odborný polštář, který by měl zabránit, že napácháte v dané oblasti velké množství hrubých bezpečnostních nebo jen s duchem technologie neslučitelných chyb.

Takže výhody:

1) Osvědčená řešení ihned po ruce. Tím se cíl SW faktory moc neliší od obecných frameworků a návrhových vzorů.

2) Není nutné psát infrastrukturní kód přímo v SW firmě (ISV). Je možné se soustředit na problémovou doménu zákazníka a ne na to, jak centrálně publikovat události nebo jak propojit různé pohledy v aplikaci tak, aby spolu dokázaly komunikovat .

3) Pokud stejnou továrnu používáte napříč všemi projekty, je zaučení nového vývojáře na dalším projektu jednodušší a rychlejší.

4) SW továrna používá praktiky osvědčené v dané technologii. Takže odlišně jsou zakódovány původně platformně nezávislá doporučení a všeobecně zaměřené vzory v SW továrně pro ASP.Net a jinak pro Silverlight. Místo popisu obecných návrhových vzorů je již vzor adaptován na konkrétní cílové prostředí.

Nevýhody:

1) Kód nemáte pod kontrolou a musíte se spolehnout, že továrna sama neobsahuje příliš kritických chyb ani otravných chyb s nižší prioritou. To platí o jakékoli abstrakci (frameworku, knihovně), kterou na projektu používáte, ale myslím, že ani v Microsoftu stále nemají SW továrny stejnou podporu jako samotný .Net Framework. A také by měla být známa alespoň orientační roadmapa SW továrny, abyste věděli, kam autoři směřují a jestli jsou si vědomi nevýhod a omezení stávající verze SW továrny. Od továrny, která se ihned po svém uvedení honosí přídomky „experimentální, testovací, zkušební“, bych dal ruce pryč. Jen ti nejodvážnější z nás si dovolí svým zákazníkům za půl roku po zatracení SW továrny jako slepé uličky tvrdit, že musí trochu poštelovat kardiostimulátor výkonného srdce aplikace a že místo plánovaných dvou hodin za přidání dvou vlastností do objednávky zákazník zaplatí dva měsíce migrace na jinou SW továrnu. Nenechal bych se zmýlit tím, že některé firmy pro odvážlivce používající jejich experimentální SW továrny a frameworky razí lichotivý titul „early adopter“ – méně korektní a pravdě bližší překlad z marketingového slovníku totiž zní „natěšený všehoschopný blbec“.

2) Univerzální řešení jako je SW továrna může být pro jednoduché aplikace zabijákem. Více času strávíte integrací aplikace do rámce vynucovaného SW továrnou než psaním obchodní logiky specifické pro aplikaci a důležité pro zákazníka.

3) U některých složitějších aplikací zjistíte, že rámec SW továrny je příliš těsný a vy potřebujete SW továrnu pro svůj projekt upravit nebo rozšířit. Náklady na rozšíření služeb , „hackování“ a ohýbaní SW továrny pro danou problematiku mohou převážit nad výhodami SW továrny. Z velké radosti nad úsporou času v raných fázích projektu, kde byla továrna použita, můžete ještě před dodáním první verze projektu a po pořádném časovém skluzu, vynuceném třeba přepisem částí továrny, které pro výkonnostních testech  nestačí stávajícím požadavkům na projekt, přejít k jadrným kletbám nad šílenou SW továrnou, v níž si úprava jednoho modulu kaskádově vynutí úpravy všech dalších modulů. Místo používání původně slibované elegantní černé skříňky s jednoduchými službami se prohrabujete nevábnými vyhřezlými vnitřnostmi mizerně navržené SW továrny.

Z výhod a nevýhod snad vyplývá můj postoj k SW továrnám. SW továrny jsou dalším evolučním stádiem na cestě, jejímž cílem je využít pro danou problematiku osvědčená řešení a postupy, nepsat stále se opakující kód nebo v každém řešení už jen změnou typu klienta (Web, Windows Forms, Silverlight, mobilní aplikace) neprocházet znovu a znovu všechny slepé vývojářské uličky specifické pro použitou technologii. Objevování objeveného ponechme těm, kterým stačí zařvat vítězoslavně heuréka, i když jsou v pořadí stí nebo tisící, kteří stejnou infrastrukturní nebo funkční trivialitu konečně zakódovali rozumným a v životním cyklu projektu dále udržovatelným způsobem.

A samozřejmě si nemyslím, že tohle evoluční stádium je konečné. :)


Share/Save/Bookmark Sunday, March 21, 2010 11:57:10 AM (Central Europe Standard Time, UTC+01:00)  #     
Comments [6]  .NET Framework | Analytické drobky | Návrhové vzory | UML


 Tuesday, February 02, 2010
Hrátky s Reaktivním frameworkem (RX extenze)

V předchozím článku jsem ukazoval, jak volat asynchronně metody z C# Posterous API v Silverlightu. C# Posterous API nabízí asynchronní zpracování pomocí jednoho z doporučovaného přístupu k asynchronním operacím v .Net Frameworku – metoda s konvenčním sufixem Async (LoadPostsAsync) spustí vykonání operace v jiném vlákně a výsledky operace jsou nabídnuty v argumentech události, která je (opět) jen dle jmenné konvence spojena s asynchronní operací (událost LoadPostsCompleted). C# Posterous API nenabízí ve svém rozhraní  metody pro podporu dalšího a již od verze 1.0 .Net Frameworku přítomného asynchronního vzoru, který je spojen s dvojicí metod začínajících prefixem Begin a End. (BeginGetRequest, EndGetRequest, BeginRead, EndRead apod.)

Dále předpokládám, že oba přístupy k vytváření asynchronních opreací znáte a že jste si vědomi i toho, jak se způsob práce s asynchronními API odlišuje od práce s běžnými synchronnními metodami.

V již odkazovaném článku bylo dobře patrné, jak je řízení toku asynchronních operaci odlišné od sady volání běžných synchronních operací.

Pro připomenutí:

posterousAccount.SitesLoaded += (o, e) =>
                      {
                          throwIfAsyncEx(e.Exception);
                          posterousAccount.PrimarySite.PostsLoaded += (_, e2) =>
                                                                          {
                                                                              throwIfAsyncEx(e2.Exception);
                                                                              Posts = (from p in e2.Value
                                                                                      select new ViewPost
                                                                                                 {
                                                                                                     Title = p.Title,
                                                                                                     Body = p.Body,
                                                                                                     Url = p.Url

                                                                                                 }).ToList();                                                                                                            
                                                                              
                                                                          };
                          posterousAccount.PrimarySite.LoadAllPostsAsync();
                      };



posterousAccount.LoadSitesAsync();

Jediné, co tento kód dělá, je, že nejprve (!) nahraje všechny blogy (příkaz k asynchronnímu nahrání posterousAccount.LoadSitesAsync(); je na posledním (!) řádku. Na prvním (!) řádku máme zpracování výsledku volání metody LoadSitesAsync, ve kterém opět nejdříve (!) lambdou přihlášenou k odběru události  PostsLoaded (posterousAccount.PrimarySite.PostsLoaded += (_, e2)) řekneme, jak zpracujeme výsledek následného (!) volání další asynchronní metody (posterousAccount.LoadSitesAsync());. Tato “inverzní“ práce s asynchronními metodami a zpracováním jejich výsledku je na hony a možná ještě dále vzdálena intuitivní práci se synchronními metodami.:-)

Zkusme se nyní podívat, jak by nám s “převrácením starších asynchronních metod z hlavy zpět na synchronní nohy” mohl pomoci RX Framework. Úplné základy v tomto článku nezazní a začátečníky odkazuji na sérii přednášek na Channel 9, kde dozvíte i zajímavé podrobnosti o genezi celého RX Frameworku  a matematické dualitě rozhraní IEnumerable a IObservable (jinými slovy o společných rysech dobře známých GoF návrhových vzorů Iterátor a Observer).

Současné příklady jsou vytvořeny v aplikaci Windows Forms pro .Net 3.5. Silverlight má své zvláštnosti a a rozchození příkladů v SL si zaslouží další článek, protože teď by řešení problémů specifických pro SL zamlžovalo cíl příkladu. Aplikace je pro .Net 3.5, protože stejná aplikace pro .Net 4.0 hlásí konflikt (ambiguous reference) mezi NF typy a RX typy.

Upozornění: Nic z toho, co napíšu neberte ani jako dogmata ani, nedej bože, jako best practices. RX Framework je v Betě, zdokumentován je mizerně a z jednoho řádku u každé metody se dá jen těžko bez dalších experimetů vytušit, co přesně metoda dělá. Tento článek je výsledkem hraní si pro účely jednoho projektu, kam se RX extenze hodí  a zjednodušují (alespoň to tak prozatím vypadá :-) ) dost rutinních činností.

Zde j výsledek našeho snažení, abychom měli motivaci se RX Frameworkem zabývat.

 var resultPosts = from sites in account.GetSites()
                              from site in sites.ToObservable()
                              from posts in site.GetPosts()
                              from post in posts.ToObservable()
                              where post.Private == false
                              select post;

Získání blogů (Sites) i blogpostů (post) je stále asynchronní, ale výsledný kód vypadá jako běžný LINQ (To Enumerable) dotaz. Žádné inverzní volání a práce s výsledkem, jen prostý dotaz, jehož zvláštností je pouze to, že v některých místech voláme metodu ToObservable.

Jak jsem dosáhl tohoto výsledku?

Podíváme-li se na první řádek, vidíme, že voláme metodu account.GetSites. Metoda GetSites součástí C# Posterous API není a jedná se o extenzní metodu. Tato extenzní metoda je zvláštní tím, že její návratovou hodnotou je je jedno z klíčových rozhraní v RX Frameworku – rozhraní IObservable<T>.

        public static IObservable<IEnumerable<IPosterousSite>> GetSites(this IPosterousAccount account)

Rozhrani IObservable má v RX Frameworku podobný význam jako rozhraní IEnumerable v celém .Net Frameworku.  Zjednodušeně můžeme rozhraní IObservable popsat jako ceduli, kterou třída implementující rozhraní dává celému světu najevo: “Miluju voyery, jestliže chcete sledovat, co se ve mně děje, dejte mi sem pozorovatele a já na sebe všechno podstatné, co se od této chvíle stane, postupně  vyzvoním ”.

Rozhraní IObservable je tedy příslib, že zainteresovaný pozorovatel dostane data, která třída podporující toto rozhraní nabízí. Svůj zájem pozorovatel deklaruje tak, že předá odkaz sám na sebe do metody Subscribe.

public interface IObservable<T> 
{
 IDisposable Subscribe(IObserver<T> observer); 
}

Pozorovatel (IObserver) reaguje (proto reaktivní framework) na informace, které jsou mu poskytnuty objektem podporujícím rozhraní IObservable.

public interface IObserver<T> 
{ 
void OnCompleted(); 
void OnNext(T value); 
void OnError(Exception exn); 
}

Metoda OnNext je na IObserver volána vždy, když Observable objekt má k dispozici další data. Metodou OnError Observable objekt signalizuje chyby a metodou OnCompleted Observeru říká “jsem u konce, nic dalšího už pro tebe nemám”.

Naše metoda GetSites tedy říká – zavolejte mě a já vám nabídnu IObservable objekt, který, až budou data k dispozici, vašemu observeru (IObserver) vydá kolekci (IEnumerable) objektů IPosterousSite.

Extenzní metoda GetSites vypadá takto:

 public static IObservable<IEnumerable<IPosterousSite>> GetSites(this IPosterousAccount account)
        {
             
            checkAccountNotNull(account);            
             var sitesEvents = Observable.FromEvent<EventArgsValue<IEnumerable<IPosterousSite>>>(handler => account.SitesLoaded += handler,
                                                                                                handler => account.SitesLoaded -= handler)
                                        .Take(GlobalConstants.DEFAULT_TAKE_EVENTS_COUNT);



             return sitesEvents.GetFinalObservableEvents(account.LoadSitesAsync);
            
        }

 

Po kontrole, zda předaný IPosterousAccount není null, využijeme pomocnou metodu Observable.FromEvent z RX Frameworku, která nám vrátí IObservable objekt. Tento IObservable objekt notifikuje případného observera o každé nastalé události sites.Loaded. V našem případě Observera notifikuje o právě jedné události, protože jsme použili metodu Take (Take(GlobalConstants.DEFAULT_TAKE_EVENTS_COUNT)) a konstanta DEFAULT_TAKE_EVENTS_COUNT má hodnotu 1. Jak si můžete všimnout, metoda FromEvent nám dovoluje s událostmi, které postupně nastávají, zacházet jako (s potenciálně nekonečnou) kolekcí hodnot. Metodě FromEvent jsme pouze museli říct, jaká třída nese argumenty událost (EventArgsValue<IEnumerable<IPosterousSite>) a poskytli jsme ji dva delegáty pro registraci/deregistraci obslužných handlerů, které nám RX Framework předá  (handler => account.SitesLoaded += handler, handler => account.SitesLoaded –= handler). U našeho volání metody Take bych ještě poznamenal, že po vyvolání první události dojde automaticky RX Frameworkem k deregistraci obslužného handleru.

Proměnná sitesEvents je IObserver tohoto typu.

IObservable<IEvent<EventArgsValue<IEnumerable<IPosterousSite>>>>

Argumenty události jsou vždy zabaleny do instance třidy IEvent, která je vydána zaregistrovanému observeru v jeho metodě OnNext. Všimněte si ale, že návratovou hodnotou metody GetSites je již Observable, který observeru předá hodnoty bez IEvent (IObservable<IEnumerable<IPosterousSite>>).

Vidíme, že na sitesEvents je volána další má extenzní metoda GetFinalObservableEvents, které je předán delegát Action ukazující na asynchronní metodu account.LoadSitesAsync, a výsledek volání GetFinalObservableEvents je vrácen klientovi.

Metoda GetFinalObservableEvents:

  public static IObservable<TEventData> GetFinalObservableEvents<TEventData>(this IObservable<IEvent<EventArgsValue<TEventData>>> sourceEvents, Action runAction)
        {
            if (sourceEvents == null)
            {
                throw new ArgumentNullException("sourceEvents");
            }
            var retObservable = new DelegateObservable<TEventData>(
                observer =>
                    {
                        
                        var eventObserver = new EventObserver<TEventData>(observer);
                        var unsubScribe = sourceEvents.Subscribe(eventObserver);
                        runAction();
                        return unsubScribe;
                    });

            return retObservable;
        }

 

Metoda GetFnalObservableEvents vrací opět Observable, ale tentokrát jde o Observable typu IObservable<TEventData>  - jinými slovy, v našem případě IObservable<IEnumerable<IPosterousSite>>. Jak je toho dosaženo? Zdrojový IObservable objekt nazvaný sourceEvents je předán instanci třídy DelegateObservable, což je v současném scénaři již ten hledaný Observable podporující rozhraní IObservable<IEnumerable<IPosterousSite>>. DelegateObservable je tedy adaptér, který převádí události zabalené do IEvent na “rozbalené” hodnoty očekávané observerem. DelegateObservable je můj pomocný IObservable, který dostává do konstruktoru lambdu představující tělo jeho metody Subscribe, abychom nemuseli reimplementovat rozhraní IObservable v různých třídách stále dokola.

Výpis třídy DelegateObservable

 public class DelegateObservable<T> : IObservable<T>
    {
        private readonly Func<IObserver<T>, IDisposable> m_subscribeDel;

        public DelegateObservable(Func<IObserver<T>, IDisposable> subscribeDelegate)
        {
            m_subscribeDel = subscribeDelegate;
            if (m_subscribeDel == null)
            {
                throw new ArgumentNullException("subscribeDelegate");
            }
        }

        #region Implementation of IObservable<out T>

        public IDisposable Subscribe(IObserver<T> observer)
        {
            if (observer == null)
            {
                throw new ArgumentNullException("observer");
            }
            
           return m_subscribeDel(observer);
        }

        #endregion
    }

Předaná lambda v našem případě vytvoří instanci třídy eventsObserver, což je observer, který bude zpracovávat přicházející události, a do konstruktoru mu podhodí observer předaný klientským kódem – eventsObserver je tedy další adaptér, který je zdopovědný za “rozbalení” dat z instance IEvent a za předání těchto dat klientskému (“konečnému”) observeru.

Třída EventObserver:

 public class EventObserver<T> : IObserver<IEvent<EventArgsValue<T>>>                        
    {
        private readonly IObserver<T> m_innerObserver;
        private bool m_exceptionOccured;

        public EventObserver(IObserver<T> innerObserver)
        {
            if (innerObserver == null)
            {
                throw new ArgumentNullException("innerObserver");
            }
            
            m_innerObserver = innerObserver;
            m_exceptionOccured = false;
        }

        #region Implementation of IObserver<T>
        

        public virtual void OnNext(IEvent<EventArgsValue<T>> eventData)
        {
            if (eventData.EventArgs.Exception != null)
            {
                OnError(eventData.EventArgs.Exception);
                return;
            }
            //Rozbalení a předání dat Observeru
            m_innerObserver.OnNext(eventData.EventArgs.Value);
        }

        public virtual void OnError(Exception exception)
        {
            m_innerObserver.OnError(exception);
            m_exceptionOccured = true;
        }

        public virtual void OnCompleted()
        {
           //Chyba ukončí sekvenci sama o sobě
            if (!m_exceptionOccured)
            {
                m_innerObserver.OnCompleted();                
            }
        }

Třída EventObserver implementuje rozhraní IObservable s těmito generickými argumenty - IObserver<IEvent<EventArgsValue<T>>> . V C# Posterous API všechny události předávají svá data v instanci třídy EventArgsValue<T>, což znamená, že pro naše účely je EventObserver univerzálně použitelný observer pro zpracování výsledků asynchronní operace.

Pro úplnost zde je výpis třídy EventArgsValue

 public class EventArgsValue<T> : EventArgs
    {
        private readonly T m_value;
        private readonly Exception m_exception;

        internal EventArgsValue(T value, Exception exception)
        {
            m_value = value;
            m_exception = exception;
        }
            
        public T Value
        {
            get
            {
                return m_value;
            }
        }

        public Exception Exception
        {
            get
            {
                return m_exception;
            }
        }
    }

V lambdě předané do objektu DelegateObservable také musíme spustit asynchronní operaci – to je volání Action delegáta runAction, který nám předala již metoda GetSites. Každý IObservable také z metody vrací objekt implementující rozhraní IDisposable – volání metody Dispose dovoluje klientovi odpojit se objektu IObservable. Lambda  předaná do instance DelegateObservable vrátí  IDisposable objekt, který je vydán po připojení EventObservera k “streamu události“ (sourceEvents).

Přidání extenzních metod k dalším třídám je triviální – zde je extenzní metoda pro IPosterousSite, která nahraje všechny blogposty.

public static IObservable<IEnumerable<IPosterousPost>> GetPosts(this IPosterousSite site)
        {            

            throwIfSiteNull(site);
            var postsEvents = Observable.FromEvent<EventArgsValue<IEnumerable<IPosterousPost>>>(handler => site.PostsLoaded += handler,
                                                                                               handler => site.PostsLoaded -= handler)
                                      .Take(GlobalConstants.DEFAULT_TAKE_EVENTS_COUNT);
            

            return postsEvents.GetFinalObservableEvents(site.LoadAllPostsAsync);
            
            

        }
 
 

A nyní se můžeme znovu podívat, jak naše API použijeme v klientském kódu – díky alternativní implementaci Query vzoru v RX frameworku  můžeme používat staré dobré známé LINQ dotazy.

Metoda loadPosts:

private void loadPosts()
        {
            toolStripStatusLabel1.Text = TEXT_LOAD_DATA_START;

            IPosterousApplication app = PosterousApplication.Current;
            IPosterousAccount account = app.GetPosterousAccount("<Posterous user name>", "Posterous password");

            var syncContext = SynchronizationContext.Current;

            var resultPosts = from sites in account.GetSites()
                              from site in sites.ToObservable()
                              from posts in site.GetPosts()
                              from post in posts.ToObservable()
                              where post.Private == false
                              select post;

            resultPosts.Subscribe(post =>
                                      {
                                          lock (m_threadsSet)
                                          {
                                              m_threadsSet.Add(Thread.CurrentThread.ManagedThreadId);
                                          }


                                          syncContext.Post(_ =>
                                                               {
                                                                   var UCpost = new UC_Post
                                                                                    {
                                                                                        Title = post.Title,
                                                                                        Body = post.Body
                                                                                    };

                                                                   flowLayoutPanel1.Controls.Add(UCpost);
                                                               },
                                                           null

                                              );
                                      },

                                  ex => syncContext.Post(_ =>
                                                             {

                                                                 throw new ApplicationException(ASYNC_EXCEPTION_TEXT, ex);
                                                             },

                                                         null),

                                  () => syncContext.Post(_ =>
                                                             {
                                                                 lock (m_threadsSet)
                                                                 {
                                                                     m_threadsSet.Run(tId => lstThreads.Items.Add(tId));
                                                                 }

                                                                 toolStripStatusLabel1.Text = TEXT_LOAD_DATA_END;
                                                             },
                                                         null

                                            ));



        }

V proměnné resultPosts jsou uloženy všechny blogposty (IPosterousPost) ze všech blogů (IPosterousSite). Blogy i blogposty jsou nahrány asynchronně, ale v klientském kódu nevidíme žádná specialitky kvůli asynchronnímu nahrávání dat. Na proměnných sites i posts v dotazu je volána další extenzní metoda z RX Frameworku ToObservable, protože jak víme, výsledkem volání asynchronních metod byly typy IEnumerable<IPosterousPost> a IEnumerable<IPosterousSite>.

Důležité je, že zpracování dotazu je opět “lazy” – to znamená, že k získání dat dojde až poté, co k resultsPosts zaregistruju svého Observera metodou Subscribe (analogie “Lazy” vyhodnocování v LINQ To IEnumerable a procházení dotazu v cyklu foreach). Metoda Subscribe má několik variant a jedna z nich nám dovoluje pro metody OnNext, OnError a OnCompleted předat delegáty, aniž bychom byli nuceni vytvářet svou třídu implementující rozhraní IObserver.

První delegát (OnNext) vezme předaný post a vytvoří pro něj UserControl, který vloží do FlowPanelu na formuláři. Ještě předtím pro zajímavost do Hashsetu ukládám identifikátory vláken, které se na zpracování dotazu podílí. S prvky na formuláři můžeme pracovat jen z UI threadu, a proto je vložení User controlu provedeno přes SynchronizationContext uložený do proměnné syncContext před spuštěním dotazu.

Druhý delegát (OnError)  pouze přes SynchronizationContext zpropaguje výjimku, která nastala při asynchronním zpracování, do UI threadu. Všimněte si, jak je zpracování výjimek jednoduché – rozdíl vynikne při srovnání s opakovaným voláním metody throwIfAsyncEx v kódu na začátku tohoto článku.

Třetí delegát (OnCompleted) naplní listbox na formuláři ID použitých threadů a změní text ve status baru.

Zde je výsledný formulář. V listboxu nahoře si můžete všimnout, že u mě byly k vykonání dotazu použity celkem 3 thready.

 

AsyncForm

 

Tím bychom mohli skončit, ale RX Framework má pro asynchronní operace ještě další zajímavou podporu. Pomocí metody Observable.FromAsyncPattern můžeme vytvořit IObservable rychle a bezpracně ze standardního a výše již zmíněného asynchronního Begin/End vzoru. V C# Posterous API metody Begin*/End* nejsou, proto je zkusme dodat pomocí extenzních metod.

Rozhraní IPosterousAccount bude obohaceno o extenzní metody BeginLoadSites a EndLoadSite.

Metoda BeginLoadSites

public static IAsyncResult BeginLoadSites(this IPosterousAccount account, AsyncCallback callback, object context)
        {
            checkAccountNotNull(account);
            var loadSiteAction = new Action(account.LoadSites);
            
            return RXEventsHelper.GetAsyncResultEx(loadSiteAction, callback, context);
       
            
        }

Jak vidíte, přesně dle konvencí .Net vzoru metoda vrací odkaz na rozhraní IAsyncResult a přijímá callBack, což je tedy u tohoto vzoru metoda, která má být vyvolána po dokončení asynchronního zpracování, a jak také vzor vyžaduje, posledním argumentem je libovolný objekt reprezentující libovolný “stavový token” operace, který v metodě End* klient používá pro korelací mezi požadavkem a odpovědí.

Veškerá práce je přenesena na metodu GetAsyncResultEx v mém RXEventsHelperu – metoda vyžaduje, abyste ji poslali v delegátu Action metodu, která má být spuštěna asynchronně.

Metoda RXEventsHelper.GetAsyncResultEx.

 public static IAsyncResult GetAsyncResultEx(Action runAction, AsyncCallback callback, object context)
        {
            if (runAction == null)
            {
                throw new ArgumentNullException("runAction");
            }

            Exception ex = null;
            
            var proxyCallback = new AsyncCallback(ar =>
                                                      {
                                                          IAsyncResult proxyResult = new AsyncResultEx(ar, runAction);                                                                                         
                                                           callback(proxyResult);
                                                      });

            return runAction.BeginInvoke(proxyCallback, context);
                      

            
                       
        }

Hlavním trikem je využití možností delegátů – každý delegát v .Net Frameworku vždy obsahuje asynchronní metody BeginInvoke a EndInvoke, které splňují nároky asynchronního vzoru. My tedy na předaném delegátu runAction zavoláme metodu BeginInvoke, ale místo klientské callBack Funkce podhodíme svou proxy funkci (proxyCallback), která po dokončení asynchronního volání připraví pro naši End metodu vlastní IAsyncResult (AsyncResultEx).

Třída AsyncResultEx zapouzdřuje původní IAsyncResult  (argument ar předaný  do konstruktoru v předešlém výpisu) a navíc, když na její instanci zavoláme metodu EndAction, na předaném delegátovi (argument runAction v předešlém výpisu) je zavolána metoda EndInvoke, čehož využije naše metoda EndLoadSites.

Třída AsyncResultEx

 public class AsyncResultEx : IAsyncResult
    {
        
        #region private variables
        private IAsyncResult m_originalAsyncResult;
        private readonly Action m_originaldelegate;
        #endregion private variables

        public AsyncResultEx(IAsyncResult origAsyncResult, Action originaldelegate)
        {
            if (origAsyncResult == null)
            {
                throw new ArgumentNullException("origAsyncResult");
            }


            m_originalAsyncResult = origAsyncResult;
            m_originaldelegate = originaldelegate;
        }

        #region properties
        public virtual IAsyncResult OriginalAsyncResult
        {
            get
            {
                return m_originalAsyncResult;
            }

        }

        public virtual Action OriginalDelegate
        {
            get
            {
                return m_originaldelegate;
            }
        }
        
        #endregion properties

        #region methods

        public virtual void EndAction()
        {
            
            if (OriginalDelegate != null)
            {
                OriginalDelegate.EndInvoke(OriginalAsyncResult);
            }                                                

        }
            
        #endregion methods
        #region Implementation of IAsyncResult

        
        public virtual bool IsCompleted
        {
            get
            {
                return m_originalAsyncResult.IsCompleted;
            }
        }

        public virtual object AsyncState
        {
            get
            {
                return m_originalAsyncResult.AsyncState;
            }
        }

        public virtual WaitHandle AsyncWaitHandle
        {
            get
            {
                return m_originalAsyncResult.AsyncWaitHandle;
            }
        }

        public virtual bool CompletedSynchronously
        {
            get
            {
                return m_originalAsyncResult.CompletedSynchronously;
            }
        }
                
        #endregion
    }

Extenzní metoda EndLoadSites

 public static IEnumerable<IPosterousSite> EndLoadSites(this IPosterousAccount account, IAsyncResult result)
        {
            checkAccountNotNull(account);
            
            var exResult = result as AsyncResultEx;
            
            if (exResult == null)
            {
                throw new ArgumentException("result");
            }
            
            exResult.EndAction();
                                               
            return account.Sites;
        }

Zde vidíme volání metody EndAction na podhozené instanci AsyncResultE. Poté metoda EndLoadSites jen vrátí kolekci Sites objektu account, protože ta  nyní již musí být po asynchronním volání naplněna daty.

Se stávající infrastrukturou si opět si můžeme rychle připravit další Begin a End metody. Zde jsou extenzní metody BeginLoadPosts a EndLoadPosts pro IPosterousSite.

public static IAsyncResult BeginLoadPosts(this IPosterousSite site, AsyncCallback callback, object context)
        {
            throwIfSiteNull(site);
            var loadPostsAction = new Action(site.LoadAllPosts);
            
            return RXEventsHelper.GetAsyncResultEx(loadPostsAction, callback, context);
        }

        public static IEnumerable<IPosterousPost> EndLoadPosts(this IPosterousSite site, IAsyncResult result)
        {
            throwIfSiteNull(site);
            var exResult = result as AsyncResultEx;

            if (exResult == null)
            {
                throw new ArgumentException("result");
            }

            exResult.EndAction();

            return site.Posts;
        }

 

A metoda loadPosts2, která dělá to samé, co předchozí metoda loadPosts, ale používá naše nové extenzní Begin/End metody.

 private void loadPosts2()
        {
            toolStripStatusLabel1.Text = TEXT_LOAD_DATA_START;

            IPosterousApplication app = PosterousApplication.Current;
            IPosterousAccount account = app.GetPosterousAccount("posterousname", "posterouspassword");

            var syncContext = SynchronizationContext.Current;            


            var resultPosts = from sites in Observable.Defer(() => Observable.FromAsyncPattern<IEnumerable<IPosterousSite>>(account.BeginLoadSites, account.EndLoadSites)())
                              from site in sites.ToObservable()
                              from posts in Observable.Defer(() => Observable.FromAsyncPattern<IEnumerable<IPosterousPost>>(site.BeginLoadPosts, site.EndLoadPosts)())
                              from post in posts.ToObservable()
                              where post.Private == false
                              select post;

            resultPosts.Subscribe(post =>
                                      {
                                          lock (m_threadsSet)
                                          {
                                              m_threadsSet.Add(Thread.CurrentThread.ManagedThreadId);
                                          }


                                          syncContext.Post(_ =>
                                                               {
                                                                   var UCpost = new UC_Post
                                                                                    {
                                                                                        Title = post.Title,
                                                                                        Body = post.Body
                                                                                    };

                                                                   flowLayoutPanel1.Controls.Add(UCpost);
                                                               },
                                                           null

                                              );
                                      },


                                  ex => syncContext.Post(_ =>
                                                             {
                                                                 
                                                                 throw new ApplicationException(ASYNC_EXCEPTION_TEXT, ex);
                                                             },

                                                         null),

                                  () => syncContext.Post(_ =>
                                                             {
                                                                 lock (m_threadsSet)
                                                                 {
                                                                     m_threadsSet.Run(tId => lstThreads.Items.Add(tId));
                                                                 }

                                                                 toolStripStatusLabel1.Text = TEXT_LOAD_DATA_END;
                                                             },
                                                         null

                                            )


                );


        }

Upozornil bych jen na dvě specialitky či zrádná místa, která (alespoň v této BETA verzi RX) dělají kód méně intuitvním, než by bylo žádoucí:

Jedná se o tyto dva řádky:

from sites in Observable.Defer(() => Observable.FromAsyncPattern<IEnumerable<IPosterousSite>>(account.BeginLoadSites, account.EndLoadSites)())  
from posts in Observable.Defer(() => Observable.FromAsyncPattern<IEnumerable<IPosterousPost>>(site.BeginLoadPosts, site.EndLoadPosts)()) 

Metoda FromAsyncPattern přijímá delegáty na naše asynchronní metody, ale místo spolehnutí se na typovou inference jsem musel generický argument předat explicitně(Observable.FromAsyncPattern<IEnumerable<IPosterousSite>>) – pokud argument nezadáte, kompilátor hlásí “ambigous reference”.

Dále je patrné, že výsledek funkce FromAsyncPattern, kterým je další funkce vracející IObservable, je předán jako argument metodě Observable.Defer. Metoda Observable.Defer zajistí, že k vyhodnocení předané funkce dojde až poté, co je k výsledkům dotazu přihlášen observer – jinými slovy, metoda Defer nám pomáhá zachovat “lazy” vyhodnocení dotazu.

Dotaz bude fungovat i v této podobě (bez Defer):

var resultPosts = from sites in  Observable.FromAsyncPattern<IEnumerable<IPosterousSite>>(account.BeginLoadSites, account.EndLoadSites)()
                              from site in sites.ToObservable()
                              from posts in Observable.FromAsyncPattern<IEnumerable<IPosterousPost>>(site.BeginLoadPosts, site.EndLoadPosts)()
                              from post in posts.ToObservable()
                              where post.Private == false
                              select post;

Ale jeho vyhodnocení už není “lazy”. Všimněte si závorek na konci výrazu - FromAsyncPattern<IEnumerable<IPosterousSite>>(account.BeginLoadSites, account.EndLoadSites)()  - výsledné IObservable získám okamžitým zavoláním funkce vrácené z metody FromAsyncPattern. Vadit vám to začne v okamžiku, kdy zkonstruujete dotaz, ihned se odpálí asynchronní volání, dojde k chybě a vy budete mít v aplikaci neošetřenou výjimku v threadu na pozadí, protože IObserver ještě není přihlášen (není možné zavolat druhého delegáta - ex => syncContext.Post(_ => { throw new ApplicationException(ASYNC_EXCEPTION_TEXT, ex); }, null), ).

 

Snad se vám tato exkurze líbila. Já ještě na RX Framework konečný názor nemám, ale něco neodbytného ve mně říká, že by mohlo jít o další LINQ, který otřese programátorským světem. :-) Některé extenze pravděpodobně zahrnu do samostatného jmenného prostoru v C# Posterous API.


Share/Save/Bookmark Tuesday, February 02, 2010 7:43:00 AM (Central Europe Standard Time, UTC+01:00)  #     
Comments [3]  .NET Framework | C# Posterous API | LINQ | Návrhové vzory | RX Extensions | Windows Forms


 Sunday, September 20, 2009
Pozvánka na podzimní kurzy (OOP, UML, základní a pokročilé návrhové vzory)

Aktualizace  10. 11. 2009-  I veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 2 je zcela obsazen včetně náhradníků. Další kurzy se budou konat na jaře 2009. Jestliže máte předběžný zájem a chcete si rezervovat místo, pište prosím na adresu petra@renestein.net.

Aktualizace 15.10.2009  - veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1 je zcela obsazen včetně náhradníků. Je možné se již hlásit pouze na kurz Pokročilé návrhové vzory a objektové principy 2.

Rád bych Vás pozval na podzimní kurzy OOP a UML a představil oficiálně InHouse kurz, který postupně vykrystalizoval z požadavků zákazníků (OOP 0 - Objektové programování a UML prakticky - rychlý úvod do světa (nejen) objektového programování).

Osnova InHouse kurzu OOP 0 – Objektové programování a UML prakticky – rychlý úvod do světa (nejen) objektového programování:

Školení má dvě varianty -  pro vývojáře i u konstrukcí a prvků jazyka UML, které jsou považovány za analytické, se dělají časté odbočky do kódu, aby vývojáři pochopili, že UML ani principy OOP nejsou nějaké nesmyslné abstrakce, ale užitečné konstrukce, které sami v programovacích jazycích používají denně.

U varianty pro „čisté“ analytiky jsou digrese do kódu minimalizovány, i když v některých místech stále zdůrazňuji, jaké znalosti z oblasti vývoje aplikací musí analytik mít, aby byl pro projekt užitečný a nevytvářel jen dokumentaci pro dokumentaci, kterou vývojáři nevyužijí a (mnohdy oprávněně) považují za nesmyslnou, drahou a projektu nic nepřinášející.

V kurzu se naučíte modelovat jednoduché i složité aplikace s využitím jazyka UML tak, aby následné kódování nebylo výletem do neznáma s nejistými výsledky, ale dobře čitelnou cestou bez temných a záludných míst vedoucích k selhání projektu.

Kurz je vhodný zvláště pro ty, kteří již nejsou spokojeni s vývojem projektů naivním "hurá" způsobem, kdy bez ohledu na složitost systému nevzniká žádný návrh a ihned se přistupuje ke kódování se všemi špatnými důsledky jako jsou podcenění technické a časové náročnosti implementace nebo vytváření drahých a nespravovatelných systémů.

Kurz je určen pro vývojáře, systémové designery, analytiky a projektové manažery, kteří se chtějí se seznámit se základními principy objektového programování a s modelováním v jazyce UML.

· Požadavky na systém a modelování pomocí případů užití (+ příklady).

· Zrychlená funkční specifikace bez zbytečných formalit – příklady.

· Diagram tříd v UML - vztahy mezi elementy diagramu (asociace. agregace, generalizace, závislost, realizace) – vše vykládáno na konkrétních příkladech z praxe + ukázky nejčastějších chyb, se kterými jsem se setkal. Třída, základní principy OOP, operace, atributy, viditelnost členů třídy. Nenásilný přechod k jednoduchým návrhovým vzorům.

· Příklady složitých diagramů tříd.

· Objektový diagram + příklady.

· Sekvenční diagramy a diagramy interakce.

· Vysvětlení stavových diagramů + výhody aplikací řízených přesně definovanými stavovými automaty.

· Diagram aktivit - modelování složitých business procesů v organizaci.

· Výhody a nevýhody UML - vyzdvižení nejvíce používaných postupů, odhození nepotřebné veteše z jazyka UML.

 

Pokud máte o kurz zájem nebo potřebujete další informace, napište prosím na adresu petra@renestein.net.


Veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1

Datum konání kurzu:  2. 11. – 4. 11. 2009

Místo konání: Hotel VILLA Praha  Okrajní 1, 100 00, Praha 10

U hotelu VILLA je  možné parkovat, po celý den máme k dispozici wifi připojení.

Na kurzu jsou samozřejmě po celý den teplé a studené nápoje a v ceně kurzu jsou obědy v hotelu.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu
Výběr z ohlasů na kurz


Veřejný kurz Pokročilé návrhové vzory a objektové principy 2

Datum konání kurzu:  23. 11. – 25. 11. 2009

Místo konání: Hotel VILLA Praha  Okrajní 1, 100 00, Praha 10

U hotelu VILLA je  možné parkovat, po celý den máme k dispozici wifi připojení.

Na kurzu jsou samozřejmě po celý den teplé a studené nápoje a v ceně kurzu jsou obědy v hotelu.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu
Výběr z ohlasů na kurzy


Share/Save/Bookmark Sunday, September 20, 2009 5:25:11 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Analytické drobky | Kurzy UML a OOP | Návrhové vzory


 Monday, February 02, 2009
Lehká imitace některých rysů windows forms aplikací v non-windows forms aplikacích

Omluvte prosím trochu kryptický název, ale lepší a hlavně výstižnější pojmenování článku mě nenapadlo. Název je stejně jen vábnička na čtenáře, proto se podívejme, co je jím míněno.

Již několikrát mně různí vývojáři tvrdili, jak nepříjemná je pro ně práce s konzolí (windows službou, dosaďte další typy aplikací dle libosti...), protože musí řešit, aby aplikace po svém spuštění ihned neskončila, a také je pro ně problematické zajistit, aby byly některé události zpracovány vždy ve stejném threadu.

Převedeme-li emocionální stížnost do věcného jazyka, zjistíme, že to, co v těchto typech aplikací chybí, jsou následující rysy běžné windows forms aplikace:

  1. Windows aplikace spustí smyčku Windows zpráv (message loop) a vývojář pouze obsluhuje události formuláře. V (Compact) .Net Frameworku nám stačí zavolat Application.Run(new Form1()) a aplikace neukončí svůj běh, dokud není uzavřen poslední formulář nebo dokud ti drsnější z nás nezavolají Application.Exit. O životní cyklus aplikace, její spuštění a ukončení, se většinou nemusíme nijak starat.
  2. Při obsluze formuláře máme po volání metody Invoke garantováno, že předaný delegát bude vykonán v takzvaném UI threadu. Hlavním účelem metody Invoke (a sesterských metod BeginInvoke a EndInvoke) je threadově bezpečná komunikace s ovládacími prvky. Ovládací prvky ve stylu windows prvků v konzolových aplikacích (windows službách) nenajdeme, ale přesto bychom i v těchto typech aplikací občas chtěli mít nástroj, který garantuje, že všechny  nebo vybrané události budou zpracovány v jednom výkonném threadu.

 

V tomto článku se objeví návrh, který pro non-windows forms aplikace přinese výše zmíněné rysy a přidá pár věcí navíc.

Pár vysvětlujících poznámek na úvod . Kód (přesněji řečeno draft k dalšímu rozpracování), který za chvíli uvidíte, má běžet na .Net frameworku a na Compact .Net Frameworku. Vím, že existují synchronizační kontexty pro thready, ale metodu Invoke jsem zmiňoval proto, že představuje společný jmenovatel pro obě prostředí, protože Compact .Net Framework je stále tím strýčkem - beznadějným sociálním případem, co nám nikdy nepřiveze žádné úhledně zabalené dárky, v nichž se skrývá třeba nádherná vlastnost SynchronizationContext.Current. Se znalostí tohoto omezení je také jasné, proč jsem nepoužil i další metody/vlastnosti dostupné jen ve "velkém" .Net Frameworku.

Dále v kódu jsou třídy obsahující ve svém názvu slovo *Console*. Nenechte se zmást, že mluvím dále jen o konzolových aplikacích, stejné třídy lze použít ve windows službě a dalších typech aplikací.

Zaveďme si nejdříve abstraktní třídu ConsoleTask, která je předkem všech zpracovávaných úloh v aplikaci. Zjednodušeně si můžeme třídu ConsoleTask a její potomky představit jako výchozí stavební prvky zapouzdřující chování analogické k vybraným a pro nás zajímavým rysům windows formulářů.

    /// <summary>
    /// Základní rozhraní pro položky zpracovávané v jedné frontě
    /// </summary>
    internal interface IExecuteWorkItem
    {
        /// <summary>
        /// Implementace metody spustí úlohu
        /// </summary>
        void Execute();
    }

    /// <summary>
    /// Bázová třída pro všechny úlohy
    /// </summary>
    abstract class ConsoleTask : IDisposable
    {
        #region Inner classes
        /// <summary>
        /// Výchozí  implementace rozhraní <see cref="IExecuteWorkItem"/>
        /// </summary>
        private class WorkThreadItem : IExecuteWorkItem
        {
            #region Private variables
            private Delegate m_del;
            private  object[] m_vals;
            #endregion Private variables


            /// <summary>
            /// Konstruktor
            /// </summary>
            /// <param name="del">Delegát, který má být spuštěn ve frontě nadřazeného objektu <see cref="ConsoleTask"/></param>
            /// <param name="vals">Argumenty delegáta</param>
            public WorkThreadItem(Delegate del, params object[] vals)
            {
                if (del == null)
                {
                    throw new ArgumentNullException("del");
                }

                m_del = del;
                m_vals = vals;

            }

            /// <summary>
            /// Metoda iniciuje vykonání předaného delegáta
            /// </summary>
            public virtual void Execute()
            {
                m_del.Method.Invoke(m_del.Target, m_vals);
            }
        }
        #endregion Inner classes


        #region private variables
        private ManualResetEvent m_event;
        private Thread m_innerWorkingThread;
        private Queue<IExecuteWorkItem> m_workQueue;
        private AutoResetEvent m_workingThreadEvent;
        private object m_lockQueueRoot;
        private bool m_continue;
        private bool m_disposed;
        #endregion private variables

        #region constructors
        /// <summary>
        /// Konstruktor
        /// </summary>
        protected ConsoleTask()
        {
            m_lockQueueRoot = new object();
            m_workQueue = new Queue<IExecuteWorkItem>();
            m_event = new ManualResetEvent(false);
            m_workingThreadEvent = new AutoResetEvent(false);
            m_innerWorkingThread = new Thread(processWorkerThread);
            m_continue = true;
            m_disposed = false;
        }

        #endregion constructors

        #region Properties
        /// <summary>
        ///<see cref="WaitHandle"/> běžící úlohy
        /// </summary>
        public WaitHandle TaskWaitHandle
        {
            get
            {
                if (m_disposed)
                {
                    throw new ObjectDisposedException("ConsoleTask");
                }

                return m_event;
            }
        }

        

        /// <summary>
        /// Metoda vrátí true, jestliže volající thread je odlišný od threadu, který vyřizuje položky zpracovávané v jedné frontě
        /// </summary>
        public virtual bool InvokeRequired
        {
            get
            {
                if (m_disposed)
                {
                    throw new ObjectDisposedException("ConsoleTask");
                }

                if (SlaveWorkingTask != null)
                {
                    return SlaveWorkingTask.InvokeRequired;
                }
            
                return (Thread.CurrentThread.ManagedThreadId != m_innerWorkingThread.ManagedThreadId);

            }

        }

        /// <summary>
        /// Volitelná instance <see cref="ConsoleTask"/>, která převezme odpovědnost za vyřizování položek zpracovávaných v jedné frontě
        /// </summary>
        public ConsoleTask SlaveWorkingTask
        {
            get;
            set;
        }
        #endregion Properties


        #region Methods
        /// <summary>
        /// Metoda garantuje, že dojde k vykonání předaného delegáta v threadu, ktrerý vyřizuje položky zpracovávané v jedné frontě
        /// </summary>
        /// <remarks>Metoda pouze zařadí položky ke zpracování a nečeká na výsledek volání delegáta. </remarks>
        public virtual void Invoke(Delegate del, params object[] vals)
        {
            if (m_disposed)
            {
                throw new ObjectDisposedException("ConsoleTask");
            }
            
            if (SlaveWorkingTask != null)
            {
                SlaveWorkingTask.Invoke(del, vals);
                return;
            }
            
            lock (m_lockQueueRoot)
            {
                m_workQueue.Enqueue(new WorkThreadItem(del, vals));
                m_workingThreadEvent.Set();
            }
        }

        /// <summary>
        /// Metoda spustí úlohu 
        /// </summary>
        /// <remarks>Spuštěním úlohy se rozumí spuštění kódu v přepsané metodě <see cref="DoInternalRun"/> v samostatném threadu. Metoda Run nevrátí řízení, dokud není úloha dokončena.</remarks>
        public void Run()
        {
            if (m_disposed)
            {
                throw new ObjectDisposedException("ConsoleTask");
            }
            
            m_innerWorkingThread.Start();
            ThreadPool.QueueUserWorkItem(obj => DoInternalRun());
            m_event.WaitOne();
        }

        
        /// <summary>
        /// Metoda ukončí úlohu
        /// </summary>
        public virtual void CloseTask()
        {
            m_continue = false;
            m_workingThreadEvent.Set();            
            m_event.Set();
        }
        
        /// <summary>
        /// Metoda pro explicitní uvolnění veškerých nepoužívaných zdrojů  - součást implementace "Disposable" vzoru
        /// </summary>
        public void Dispose()
        {
            if (m_disposed)
            {
                return;
            }
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// "Destruktor" - součást implementace "Disposable" vzoru
        /// </summary>
        ~ConsoleTask()
        {
            Dispose(false);
        }

        /// <summary>
        /// Interní implementace "Disposable" vzoru
        /// </summary>
        /// <param name="disposing">true - jestliže je metoda volána z metody Dispose, false, pokud je volána z destruktoru - metody Finalize</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                try
                {
                    ((IDisposable)m_workingThreadEvent).Dispose();
                    ((IDisposable)m_event).Dispose();                    
                     m_disposed = true;
                }
                catch (Exception e)
                {
                    
                    Trace.WriteLine(e);
                }
                
            }
        }

        /// <summary>
        /// Metoda, která musí být přepsána v odvozených třídách a která obsahuje logiku specifickou pro každou úlohu
        /// </summary>
        protected abstract void DoInternalRun();

        /// <summary>
        /// Obsluha fronty položek, které mají být zpracovány ve stejném threadu
        /// </summary>
        private void processWorkerThread()
        {

            const int EXPECTED_MINIMUM_ITEMS = 1;

            while (m_continue)
            {
                m_workingThreadEvent.WaitOne();
                if (!m_continue)
                {
                    continue;
                }

                int m_count = EXPECTED_MINIMUM_ITEMS;
                IExecuteWorkItem nextItem = null;

                while (m_count > 0)
                {
                    lock (m_lockQueueRoot)
                    {
                        m_count = m_workQueue.Count();

                        if (m_count != 0)
                        {
                            nextItem = m_workQueue.Dequeue();
                        }
                    }


                    try
                    {
                        if (nextItem != null)
                        {
                            nextItem.Execute();
                        }
                            
                    }
                    catch (Exception ex)
                    {
                        Trace.WriteLine(ex);
                    }

                    m_count--;
                }


            }
        }

        #endregion Methods
    }

 

Abstraktní třída ConsoleTask obsahuje ve svém veřejném rozhraní metodu Run, kterou spustíme úlohu. Metoda Run je šablonovou metodou (Template method), protože obsahuje závazný scénář pro veškeré odvozené úlohy. Potomci třídy ConsoleTask do scénáře vstupují na přesně vymezeném místě - v metodě DoInternalRun, která je deklarována jako abstraktní a všechny konkrétní odvozené třídy ji musí přepsat a doplnit vlastní logiku. Třída ConsoleTask tedy garantuje, že je vždy nejprve spuštěn thread vyřizující požadavky, které mají být vykonány ve stejném threadu (podrobný popis viz níže), poté je třída ThreadPool použita ke spuštění kódu v metodě DoInternalRun v jiném threadu a nakonec aktuální thread pozastavíme čekáním na signalizaci instance synchronizačního objektu ManualResetEvent (proměnná m_event). Ve vlastnosti TaskWaitHandle vydáváme stejný objekt ManualResetEvent, který může jiný thread využít k synchronizaci svého běhu s instancí třídy odvozené od třídy ConsoleTask. Tím simulujeme pro uživatele objektů odvozených z třídy ConsoleTask spuštění smyčky zpráv, protože aplikace není ukončena po zavolání metody Run. Za ukončení běhu úlohy zodpovídá metoda CloseTask - metoda uvolní pracovní thread vyřizující frontu požadavků nastavením proměnné m_continue na false a signalizací synchronizační primitivy workingThreadEvent. Dále metoda CloseTask přes signalizaci synchronizačního objektu v proměnné m_event informuje o dokončení celé úlohy - thread pozastavený v metodě Run bude uvolněn.

Třída ConsoleTask dále obsahuje definici privátní třídy WorkThreadItem, která implementuje rozhraní IExecuteWorkItem a má roli adaptéru. Instance třídy WorkThreadItem jsou jednotlivé položky, které mají být vykonány v jednom pracovním threadu. Adaptérem je třída WorkThreadItem proto, že převádí rozhraní jakéhokoli předaného delegáta na rozhraní IExecuteWorkItem. Po volání metody Execute objektu WorkThreadItem je vykonána metoda, na kterou ukazuje delegát.

Jméno vlastnosti InvokeRequired by mělo znít povědomě - metoda vrátí true, jestliže thread, který zjišťuje hodnotu vlastnosti , je odlišný od threadu, který zpracovává položky typu IExecuteWorkItem. Thread poté může použít metodu Invoke, která zajistí, že předaný delegát bude vykonán v pracovním threadu. Je to zmíněno i v dokumentaci metody Invoke, ale zde zdůrazním, že metoda Invoke zařadí pouze novou položku do fronty ke zpracování a dá signál pracovnímu threadu, že je dostupná další položka voláním metody Set na proměnné m_workingThreadEvent, což je instance synchronizační primitivy AutoResetEvent. Metoda Invoke nečeká na výsledek volání delegáta a ani není zaručeno, že po návratu z metody Invoke byl již předaný delegát vykonán. Samotná obsluha fronty položek, které mají být vykonány v jednom threadu, je soustředěna do metody processWorkerThread.

U metody  Invoke a vlastnosti InvokeRequired si můžete všimnout podmíněné delegace volání na instanci ve vlastnosti SlaveWorkingTask. Jestliže vlastnost SlaveWorkingTask není null, je odpovědnost za zpracování položek přenesena na jinou instanci třídy ConsoleTask. Jednotlivé tasky mohou tvořit zárodečný řetězec odpovědnosti (Chain of responsibility) a za chvíli uvidíme, k čemu můžeme toto předávání odpovědnosti na jiné instance ConsoleTask využít.

Třída ConsoleTask také implementuje běžný .Net "Disposable" vzor pro uvolňování prostředků (rozhraní IDisposable, chráněná metoda Dispose a destruktor - metoda Finalize).

Mimikry konzolové aplikace, která se v rámci námi vykolíkovaného seznamu požadavků snaží vydávat za windows forms aplikaci, vylepšíme zavedením jednoduché fasády (vzor facade), která bude simulovat metodu Application.Run.

 

    /// <summary>
    /// Facade s rozhraním pro spuštění úkolu
    /// </summary>
    class ConsoleApplication
    {
        /// <summary>
        /// Metoda spustí předaný úkol (Fasáda ke spuštění úloh napodobující známou metodu Application.Run z Windows Forms aplikací)
        /// </summary>
        /// <param name="task">Úkol, který má být spuštěn</param>
        public static void Run(ConsoleTask task)
        {
            if (task == null)
            {
                    throw new ArgumentNullException("task");
            }
            
            task.Run();
        }
    }

Jak vidno,  metoda Run zcela deleguje vykonání na předaný ConsoleTask.

Jak se prozatím s naším modelem pracuje? Nejlepší bude napsat si  potomka třídy ConsoleTask  a zjistit to.  Zkusme vytvořit úlohu, která na Compact .Net Frameworku zpracuje příchozí SMS.

 

    /// <summary>
    /// Třída pro zpracování přijatých SMS
    /// </summary>
    class SMSTask : ConsoleTask
    {
        #region private variables
        private MessageInterceptor m_interceptor;
        #endregion private variables

        #region Methods
        /// <summary>
        /// Metoda začne sledovat SMS
        /// </summary>
        protected override void DoInternalRun()
        {
            m_interceptor = new MessageInterceptor(InterceptionAction.Notify, false);
            ThreadPool.QueueUserWorkItem(
                (obj) =>
                        m_interceptor.MessageReceived += m_interceptor_MessageReceived);

            
            
        }

        /// <summary>
        /// Obslužná metoda uálosti <see cref="MessageInterceptor.MessageReceived"/>
        /// </summary>
        /// <param name="sender">Odesílatel události</param>
        /// <param name="e">Argument události</param>
        private void m_interceptor_MessageReceived(object sender, MessageInterceptorEventArgs e)
        {

            if (InvokeRequired)
            {
                Invoke((Action<SmsMessage>)(handleMessage), e.Message as SmsMessage);
            }

            else

            {

                  handleMessage(e.Message as SmsMessage);

            }

} public override void CloseTask() { m_interceptor.Dispose(); base.CloseTask(); } /// <summary> /// Zpracování SMS /// </summary> /// <param name="message">SMS zpráva ke zpracování</param> private void handleMessage(SmsMessage message) { Console.WriteLine(message.Body); CloseTask(); } #endregion methods }

Autor tříd odvozených z bázové třídy ConsoleTask má lehkou práci, protože se soustředí jen na úkol (příjem SMS) a ne na to, že jeho kód bude vykonán v konzolové aplikaci. V přepsané metodě si přihlásíme odběr události MessageReceived - zde je událost přihlášena přes ThreadPool, ale není to nutné. Obslužná metoda události MessageReceived (m_interceptor_MessageReceived)  po příjmu SMS zaručí, že SMS budou vždy zpracovány ve stejném pracovním vlákně použitím vlastnosti Invoke Required a Invoke. Jestliže je událost vyvolána v jiném než pracovním threadu obsluhujícím frontu položek ke zpracování, zavoláme metodu Invoke, které předáme delegáta ukazujícího na metodu handleMessage. K vytvoření delegáta jsme použili standardního generického delegáta Action<T>, kde jsme za generický parametr T dosadili třídu SmsMessage, jejíž instanci přijímá jako argument metoda handleMessage. Přepsali jsme i metodu CloseTask, která uvolní interceptora pro příjem zpráv a poté vyvolá implementaci metody CloseTask z bázové třídy. Zde je úloha ukončena po příjmu první zprávy voláním CloseTask z metody handleMessage, ale způsob ukončení úlohy je zcela v rukou vývojáře konkrétní úlohy.

Poznámka na okraj: U naší třídy SMSTask by bylo vhodné, když chceme přijmout jen jednu SMS, ihned si odhlásit odběr dalších zpráv, nebo si v interní proměnné nastavit, že již zpráva byla přijata a další zprávy nepředávat ke zpracování.

Novou úlohu spustíme tímto nezáludným a pro vývojáře windows forms aplikací povědomým kódem:

 

    class Program
    {
        static void Main(string[] args)
        {
            SMSTask smsTask = new SMSTask();
            ConsoleApplication.Run(smsTask);
        }
    }

Na vývojářské práci je nejlepší, že poté, co máte nějaký nosný nápad, můžete jej rozvíjet ad libitum. Co když chceme ve stejné aplikaci nejen přijímat SMS, ale také reagovat na události v objektu, který nás informuje o spuštěných aplikacích uživatele. Nebo chceme sledovat přes třídu SystemState informace o příchozích hovorech? Napráskat vše do jedné instance potomka třídy ConsoleTask "ResimVzdyckyVsechnoNaJednomMisteAJsemTotalneVPohodeVoe" je sice řešením, ale i jen laxním zastáncům vágně formulovaného principu jedné odpovědnosti třídy (zdravím Aleši :) ) se právě teď nasucho aktivoval podmíněný reflex, protože vědí, že při správě takové aplikace po kolegovi-pohodářovi je vztekem podmíněné zoufalecké uslintávání a hlasité nadávání to nejmenší. :-)

Chceme určitě zachovat stávající strukturu aplikace, chceme spouštět libovolné množství různorodých úloh a navíc chceme mít možnost zpracovávat položky napříč jednotlivými úlohami ve stejném threadu - pracovní frontě. Úkol jako stvořený pro jednu z možných nenásilných inkarnací návrhového vzoru Composite v aplikaci.

 

    /// <summary>
    /// Třída reprezentující kompozitní úlohu - viz návrhový vzor Composite
    /// </summary>
    class CompositeTask : ConsoleTask
    {
        #region private variables
        private ICollection<ConsoleTask> m_tasks;
        #endregion private variables

        #region Constructors
        public CompositeTask(ICollection<ConsoleTask> tasks)
        {
            if(tasks == null)
            {
                throw new ArgumentNullException("tasks");
            }
            if (tasks.Count == 0)
            {
                throw new ArgumentException("One or more tasks are required");
            }
            m_tasks = tasks;
        }
        #endregion Constructors

        #region Methods
        /// <summary>
        /// Spuštění všech úkolů
        /// </summary>
        protected override void DoInternalRun()
        {
            foreach (var task in m_tasks)
            {
                ConsoleTask task1 = task;
                task1.SlaveWorkingTask = this;
                ThreadPool.QueueUserWorkItem((obj) => task1.Run());
                
            }
        }
        /// <summary>
        /// Metoda ukončí všechny úkoly
        /// </summary>
        /// <remarks>Metoda pouze zavolá metodu CloseTask na všech předaných objektech <see cref="ConsoleTask"/>, ale nestará se o výsledek volání</remarks>
        public override void CloseTask()
        {
            foreach (var task in m_tasks)
            {
                task.CloseTask();
            }
            
            base.CloseTask();
        }
        #endregion Methods
    }

Metoda CompositeTask je také potomkem třídy ConsoleTask, a proto můžeme ve zbytku aplikace pracovat se stejným rozhraním, na které jsme zvyklí. Jednoduchá i složená úloha mají stejné rozhraní, takže si klient tříd nemusí být skládání úloh vědom, což je mimochodem jedna z hlavních motivací pro zavedení návrhového vzoru Composite. V konstruktoru očekáváme odkaz na kolekci dceřiných úkolů. V metodě DoInternalRun zavoláme v cyklu metodu Run všech předaných úkolů. Ještě před voláním metody Run  ale nastavíme u každé úlohy vlastnost SlaveWorkingTask na aktuální objekt CompositeTask, což nám zaručí, že veškeré položky ze všech jednotlivých úloh vložené do pracovní fronty voláním metody Invoke budou zpracovány v jediném pracovním vlákně CompositeTask. Zde vidíme jeden z důvodů, proč máme vlastnost SlaveWorkingTask a proč třída ConsoleTask ve členech Invoke a InvokeRequired nejprve kontroluje, jestli má zpracovat požadavek ve své pracovní frontě, anebo existuje jiný vhodný objekt  - "otrok" (SlaveWorkingTask), který se o položky postará sám. Metoda CloseTask opět nejprve zavolá metodu CloseTask na všech objektech ConsoleTask, ze kterých je aktuální instance třídy CompositeTask složena.

Opět poznámka: Snad si rozumíme v tom, že navržená třída CompositeTask není jediná možná. Jiná třída CompositeTask2 nemusí přesměrovávat pracovní frontu na sebe, další po uzavření úloh nejprve vyčká na ukončení všech dceřiných úloh. Další scénáře jistě nalezne laskavý čtenář sám. :-)

 

Než třídu CompositeTask vyzkoušíme, vytvoříme si dalšího potomka Consoletask, který bude zpracovávat pravidelně vyvolávanou událost z našeho objektu.

Zde je jednoduchá "demo" třída, jejíž srdce tiká v rytmu události AliveEvent.

 

 

class MyEventClass
    {
        public event EventHandler<EventArgs> AliveEvent;
        private bool m_continue;
        private const int INTERVAL = 1000;
        private object m_lockObj;

        public MyEventClass()
        {
            m_continue = true;
            m_lockObj = new object();
        }

        
        public void Start()
        {
            ThreadPool.QueueUserWorkItem((state) =>
                                             {
                                                 while (m_continue)
                                                 {
                                                     Thread.Sleep(INTERVAL);
                                                     AliveEvent(this, new EventArgs());
                                                 }
                                                     
                                             });
                                            
            
            
        }        

        public void Stop()
        {
            lock (m_lockObj)
            {
                m_continue = false;    
            }
            
        }
        protected void OnAliveEevent(EventArgs e)
        {
            if (AliveEvent != null)
            {
                AliveEvent(this, e);
            }
        }
    }

 

Naše nová konkrétní úloha zpracovává události instance MyEventClass

 

 

    class ConcreteTask : ConsoleTask
    {

        private const int HEART_BEAT_LIMIT = 10;
        
        private MyEventClass m_evClass;
        private int m_heartBeatcount;
        private bool m_processEvent;


        protected override void DoInternalRun()
        {
            m_evClass = new MyEventClass();
            m_heartBeatcount = 0;
            m_processEvent = true;

            m_evClass.Start();
            m_evClass.AliveEvent += evClass_AliveEvent;

             
        }

        private void evClass_AliveEvent(object sender, EventArgs e)
        {
            
            Action myAction = (Action) (

                                           () =>
                                               {
                                               
                                                        if (!m_processEvent)
                                                        {
                                                            return;
                                                        }
                                                    

                                               
                                                   Console.WriteLine("Event fired");

                                                   m_heartBeatcount++;

                                                   if (m_heartBeatcount >= HEART_BEAT_LIMIT)
                                                   {
                                                       m_evClass.Stop();
                                                       m_processEvent = false;
                                                       CloseTask();
                                                   }
                                               }
                                       );
            if (InvokeRequired)
            {
                Invoke(myAction);

            }
            else
            {

                myAction();
            }            

        }
        
    }

Jenom pro zajímavost je ukázáno, že metodě Invoke můžeme předat složenou (statement) lambdu (nebo anonymní metodu).

 

Spuštění více úloh pomocí třídy CompositeTask není odlišné  od spuštění jedné úlohy.

 

class Program
    {
        static void Main(string[] args)
        {
            var compTask = new CompositeTask(new List<ConsoleTask>
                                                 {
                                                     new SMSTask(),
                                                     new ConcreteTask()
                                                 }); 

            ConsoleApplication.Run(compTask);
        }
    }

Znovu opakuji, že článek měl za cíl ukázat, jak transponovat do jiného aplikačního rámce postupy, které Windows Forms vývojáři dobře ovládají a o kterých mi tvrdili, že jsou "přirozené". Další rozpracování těchto draftů napsaných v půlnoční chvilce nespavosti je už jen variací předvedených postupů. ;-)


Share/Save/Bookmark Monday, February 02, 2009 2:51:15 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  .NET Framework | Compact .Net Framework | Návrhové vzory | Windows Forms


 Tuesday, November 11, 2008
Pozvánka na kurzy - nový kurz Pokročilé návrhové vzory a objektové principy 2

Rád bych Vás pozval na další běh kurzu Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1 a hlavně Vás chci seznámit se zcela novým kurzem Pokročilé návrhové vzory a objektové principy 2.

Kurz Pokročilé návrhové vzory a objektové principy 2 je volným pokračováním kurzu Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1. Pojetím kurzu Pokročilé návrhové vzory a objektové principy 2 jsem se snažil vyhovět účastníkům předchozího kurzu, kteří mi, volně parafrázováno, říkali: "Nejlepší jsou ty části, kde probíráme jeden příklad za druhým a kde říkáte  - takto to dělám já." Můj zákazník, můj pán (zvláště, jestliže se v záměrech zcela shodneme :-D) - nový kurz je prošpikován příklady, tipy, kódem, vzorovými aplikacemi. Budu se těšit na oponeturu mých postupů. ;-)

Datum konání kurzu:  9. 3. - 11. 3. 2009

Místo konání: Hotel VILLA Praha  Okrajní 1, 100 00, Praha 10

U hotelu VILLA je  možné parkovat, po celý den máme k dispozici wifi připojení.

Pro jistotu dodám, že na kurzu jsou samozřejmě po celý den teplé a studené nápoje a v ceně kurzu jsou obědy v hotelu.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Výběr z ohlasů na předchozí kurz

Předpoklady pro absolvování kurzu:

  1. Znalost alespoň jednoho z rodiny "C" jazyků (C#, Java) - příklady na školení jsou v jazyce C#.
  2. Částečná znalost UML = neutečete zděšeni z kurzu, když zobrazím diagram tříd.
  3. Nenávist ke kariéře zručného klikače a zaškrtávače ve vizuálních návrhářích a "wizardech", co s velkou vášní vytváří jedno strhující uživatelské rozhraní pro číselníky za druhým.
  4. Vhodné, nikoliv však nutné, je i absolvovat nejdříve školení Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1.
Program kurzu
  • Layer Supertype pro další vrstvy aplikace – vrstva pro řízení procesů a business transakcí.
  • Deklarativní změny v logice procesů v nasazené aplikaci prováděné samotným uživatelem.
  • Evidence historie objektů - různé přístupy.
  • Vlastní správce historie pro .Net Framework a Javu.
  • Řešení konkurenčního přístupu k datům.
  • Optimistická konkurence - různé implementace.
  • Pesimistická konkurence - různé implementace.
  • Pesimistická konkurence - různé implementace.
  • Konkurence napříč objektovými modelem - "Coarse grained lock" - různé implementace.
  • Thread Specific Storage – vlastní řešení.
  • Modelovani uživatelem zadávaných výběrových podminek (např. uživatelem definované sestavy nad objednávkami) – můj „Conditions“ vzor.
  • Návrh a implementace netriviálního právového frameworku.
  • Různé způsoby vyhodnocování práv - změna logiky za běhu aplikace.
  • Kde všude se nám hodí myšlenky návrhového vzor Accounting - modelování business aplikací jako množiny souvisejících transakcí.
  • Návrhové vzory Query a Repository a jejich vazba na „Conditions“ vzor.
  • Různé přístupy k vytváření uživatelského rozhraní - Model-View-Controller, Model-View-Presenter, Passive View - můj vlastní Form Controller.
  • Aplikace založené na pluginech – vzorové přístupy a doporučení.
  • Správa "cizích" pluginů/služeb ve vlastních aplikacích.
  • Vzor Component Configurator - správa pluginů.
  • Vzor Interceptor - ukázky business aplikací, které jsou rozšiřovány za běhu aplikace s minimálním úsilím a bez strastí opakovaného nasazení aplikace.
  • Kdy použít vzor Special Case?
  • Remote Facade a Data Transfer Object - různé přístupy k distribuované aplikaci.
  • Vzory pro zpracování požadavků na aplikaci-službu.
  • Kódování vzoru Acceptor-Connector.
  • Asynchronous Completion Token - vlastní pomocné objekty pro zjednodušení asynchronních úloh.
  • Kódování vzoru Proactor.
  • Kódování vzoru Reactor.
  • Thread Safe Interface - co pro nás znamená v moderních prostředích (Java a .Net Framework)
  • Co jsou takzvané “Enterprise segmenty” v business aplikacích?
  • V průběhu celého kurzu - kompletní případová studie existující business aplikace, v níž jsou zakódovány postupy zmiňované na kurzu - dlouhá procházka kódem. :)

 

Kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1

Datum konání kurzu:  20. 4. - 22. 4. 2009

Místo konání: Hotel VILLA Praha  Okrajní 1, 100 00, Praha 10

U hotelu VILLA je  možné parkovat, po celý den máme k dispozici wifi připojení.

Pro jistotu dodám, že na kurzu jsou samozřejmě po celý den teplé a studené nápoje a v ceně kurzu jsou obědy v hotelu.

Organizační informace ke kurzu

Program kurzu

Výběr z ohlasů na kurz


Share/Save/Bookmark Tuesday, November 11, 2008 4:38:10 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Analytické drobky | Kurzy UML a OOP | Návrhové vzory | UML


 Tuesday, October 14, 2008
Prezentace o "netradičních" návrhových vzorech pro CZ JUG ke stažení

Pro CZJUG jsem přednášel o "netradičních" vzorech - myšleno méně známých  vzorech a možná málo známých aspektech provařených vzorů. Tímto ještě jednou děkuji Dagimu a lidem ze SUNu za pozvání  na příjemnou akci i za jejich tolerantní přístup k mému, s nadsázkou,  mluvení o provaze (=C#, Visual Studio) v domě oběšencově (respektive z pohledu konkurence určitě vhodného kandidáta na business oběšence). :-)

Prezentaci si můžete stáhnout v ppt formátu nebo v pdf přímo na stránkách CZJUG.


Share/Save/Bookmark Tuesday, October 14, 2008 7:18:42 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Analytické drobky | Návrhové vzory | UML


 Sunday, October 05, 2008
Adaptéry pro funktory v C++ => Adaptéry pro funkce v C#

V C++ je snadné napsat takzvané adaptéry pro funkce, respektive pro funktory - objekty, chovající se jako funkce. K čemu jsou adaptéry dobré? Představme si, že máme napsanou funkci equal_to, která přijímá dva argumenty a vrátí true, jestliže jsou oba argumenty shodné, jinak vrátí false. Jedná se tedy o binární funktor, protože přijímá dva argumenty. Nyní potřebujeme pomocí stl metody find_if vyhledat v naší kolekci všechny prvky, jejichž hodnota je rovna 10. Podmínku v metodě find_if musí představovat unární funktor (funktor přijímající jeden argument - prvek v kolekci - a vracející true jen v případě, že prvek v kolekci podmínku splňuje). Je zřejmé, že binární funktor nemůžeme použít na místě, kde je očekáván unární funktor. V C++ můžeme ale v této situaci namísto psaní dalšího unárního jednoúčelového funktoru využít speciálního adaptéru, jehož účelem je konverze binárního funktoru na unární. Adaptér, který přijde vhod pro naše účely, se jmenuje binder1st (zde by bylo možné použít i adaptér binder2nd). Adaptér binder1st očekává, že mu předáte binární funktor, který má být převeden na unární  a hodnotu, která má být vždy použita jako první argument (proto ...1st) při volání binárního funktoru. Adaptér binder2nd se od adaptéru binder1st liší jen tím, že předaná hodnota bude použita vždy jako druhý argument předaného binárního funktoru. Jinými slovy - při volání funktoru binder1st je kolekce spokojena, že dostala unární funktor, ale náš funktor binder1st interně deleguje volání na binární funktor, kterému předá jako první argument hodnotu, kterou jsme zadali při vytvoření adaptéru binder1st, a jako druhý argument objekt z kolekce, na kterém se má otestovat platnost podmínky.

binder1st<equal_to<int> > equalPredicate = bind1st(equal_to<int>(), 10);
iterator it1 = find_if(v1.begin(), v1.end(), equalPredicate);

V předchozím kódu jsme vytvořili adaptér (unární funktor) nazvaný equalPredicate, který zprostředkovává přístup k binárnímu funktoru equal_to. Skutečnost, že je  funktor equal_to binárním funktorem, poznáme z jeho deklarace.

template<class Type>
   struct equal_to : public binary_function<Type, Type, bool> 
   {
      bool operator()(
         const Type& _Left, 
         const Type& _Right
      ) const;
   };

Na druhém řádku  příkladu adaptér equalPredicate předáme funkci find_if, která porovná každý element v kolekci (const Type&  _Right)  s hodnotou 10. Funkce vrátí první prvek, který vyhoví podmínce _Left==Right  (konkrétně v našem případě jde o podmínku  10 == PrvekVKolekci). Konstantní hodnota 10 byla předána funkci bind1st a bude  představovat při každém volání "adaptovaného" funktoru equal_to  adaptérem equalPredicate  hodnotu argumentu _Left operátoru(). Funkce bind1st je "syntaktickým cukrem", který zjednodušuje vytváření adaptéru, protože nemusíme specifikovat všechny typové parametry adaptéru binder1st, ale spolehneme se na typovou inferenci provedenou kompilátorem.

Konec rychlé exkurze do C++. I v C# nám mohou adaptéry pro delegáty přijít vhod. Představme si, že již máme napsanou třídu, která vrací výsledek porovnání dvou hodnot ("je menší než", "je větší než").

    static class ComparerEx
    {
        public static bool GreaterThan<T>(T a, T b)
        {
            return Comparer<T>.Default.Compare(a, b) > 0;
        }

        public static bool LessThan<T>(T a, T b)
        {
            return Comparer<T>.Default.Compare(a, b) < 0;
        }
    }

Funkce chceme použít v LINQ podmínkách (např. můžeme chtít z kolekce celých čísel vrátit jen všechna čísla, jež jsou větší než 10). Ale také můžeme chtít sadu podmínek, které můžeme libovolně kombinovat a skládat tak jednoduše výrazy typu "všechny hodnoty z kolekce, jež jsou větší než 20, ale menší než 90". Stejně tak můžeme chtít za chvíli podmínku znegovat a máme zájem o hodnoty nepatřící do intervalu 20-90. Namísto psaní "jednoúčelových" (i anonymních) metod si můžeme jednotlivé podmínky předpřipravit a pomocí adaptérů pro delegáty je skládat do složitějších podmínek. Také můžeme chtít stejnou podmínku použít při restrikci v LINQu (Where extenze pracující s delegátem typu Func<  >) i při práci se staršími metodami (např. FindAll u List<T>), které očekávají odkaz na delegáta typu Predicate. To vše nám speciální adaptéry pro delegáty v C# umožní.

Nejprve se podívejme na použití adaptérů.

 

class Program
    {
        static void Main(string[] args)
        {

            
            Random rand = new Random();

            //Vygenerování náhodných čísel v rozsahu 1..100
            List<int> myList = new List<int>(Enumerable.Range(1, 100).Select((i) => rand.Next(1, 100)));

            //Vytvoření predikátu pro where část LINQ dotazu (všechna čísla, kromě čísel v rozsahu 10 - 90
            var predicate = FuncExtension.Bind2nd<int, int, bool>(10, ComparerEx.GreaterThan);
            predicate = FuncExtension.And(predicate, FuncExtension.Bind2nd<int, int, bool>(90, ComparerEx.LessThan));
            predicate = FuncExtension.Not(predicate);
            
            //LINQ dotaz  - v selectu je do anonymního typu vyzvednut i index prvku v kolekci
            var result = myList
                                .Where(predicate)
                                .Select((elem, index) => new {elem, index});
                         

            //Výpis LINQ dotazu
            foreach (var res in result)
            {
                
                Console.WriteLine("{0}:{1}", res.index, res.elem);
                
            }

            //Ukázka konverze podmínky (Func<?, bool> na delegáta typu Predicate očekávaného funkcí FindAll
            var vals = myList.FindAll(FuncExtension.ToPredicate(predicate));
            
            //Musíme dostat stejné výsledky jako v předchozím dotazu s využitím LINQu
            foreach (var val in vals)
            {
                Console.WriteLine(val);
            }

            Console.ReadLine();
        }
    }

V příkladu jsme si naplnili myList náhodnými čísly v intervalu od 1 do 100. Proměnná predicate představuje podmínku.

Použitím adaptéru Bind2nd(FuncExtension.Bind2nd<int, int, bool>(10, ComparerEx.GreaterThan);) vytvoříme podmínku "všechna čísla větší než  10". Vidíme, že jsme funkci ComparerEx.GreaterThan, která očekává dva argumenty, "adaptovali-převedli" na funkci (přesněji řečeno na delegáta), který očekává jeden argument. Druhým argumentem funkce ComparerEx.GreaterThan je vždy konstantní hodnota 10 předaná  při volání funkce Bind2nd.

V dalším kroku vytvoříme podmínku ("všechna čísla menší než 90" - FuncExtension.Bind2nd<int, int, bool>(90, ComparerEx.LessThan)); ) a zkombinujeme ji s předchozí podmínkou pomocí speciálního adaptéru, který představuje operátor And (FuncExtension.And(predicate, FuncExtension.Bind2nd<int, int, bool>(90, ComparerEx.LessThan))). Operátor And je pro zbytek aplikace stále jen obyčejným (unární) delegátem na funkci, která přijímá jeden argument a vrací true nebo false. Nyní máme tedy podmínku "všechna čísla větší než 10 a menší než 90".

Naše konečná podmínka ale má mít podobu (""všechna čísla s výjimkou čísel větších než 10 a menších než 90"). Proto použijeme další speciální adaptér Not, který v předchozích krocích sestavenou podmínku zneguje (FuncExtension.Not(predicate);)

Z kolekce myList vybereme přes LINQ všechna čísla splňující podmínku (proměnná predicate s definicí podmínky je argumentem extenzní metody Where) a vypíšeme je do konzole.

Nakonec ještě stejnou podmínku chceme předat metodě FindAll. Metoda FindAll ale očekává delegáta nazvaného Predicate, a proto použijeme další "adaptující" funkci ToPredicate, který stávající definici podmínky konvertuje na Predicate.

Jak adaptéry pracují? Podívejme se na funkci Bind2nd.

 public static Func<T0, R> Bind2nd<T0, T1, R>(T1 bindValue, Func<T0, T1, R> originalFunc)
        {
            return (arg => originalFunc(arg, bindValue));
        }

Bind2nd je generická funkce, která jako argument (T1 bindValue) očekává hodnotu, která bude představovat vždy druhý argument adaptovaného delegáta (Func<T0, T1, R> originalFunc - funkce přijímající dva argumenty, první typu T0, druhý typu T1 a vracející R). Funkce vrátí nového delegáta (Func<T0, R>), který ukazuje na funkci očekávající jeden argument  typu  T0 a vracející instanci generického typu R. Delegát při svém spuštění pouze vezme předaný argument (arg) a poskytne jej jako první argument delegátovi originalFunc, kterému současně vždy předá jako druhý argument hodnotu v původním argumentu bindValue.

Podobně fungují i další adaptéry. Pro zajímavost se podívejme na adaptér ToPredicate, který z předaného delegáta vytvoří delegáta typu Predicate, čehož jsme využili v předchozím příkladu.

public static Predicate<T> ToPredicate<T>(Func<T, bool> originalFunc)
        {
            return arg => originalFunc(arg);
        }

Funkce očekává ve svém argumentu originalFunc odkaz na delegáta typu Func, který přijímá jeden argument typu T a vrací bool. My vrátíme delegáta typu Predicate<T>, přičemž vrácený lambda výraz deleguje vykonání funkce na původního delegáta originalFunc. Pro zbytek aplikace je delegát Func<T, bool> skryt za rozhraním adaptéru Predicate<T>, který nám pomohl pro funkci FindAll přeložit podmínku "v neznámém jazyce" do srozumitelné řeči.

Následuje kompletní výpis kódu adaptérů. To, co nám prozatím chybí, je ekvivalent funkce bind1st (bind2nd) z C++, který by nám zjednodušil zápis podmínek bez nutnosti zadávat "ručně" generické argumenty. Ale o tom popřemýšlím zase "někdy jindy". :-)

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FunctionExtensions
{
    static class FuncExtension
    {
        
        public static Func<T1, R> Bind1St<T0, T1, R>(T0 bindValue, Func<T0, T1, R> originalFunc)
        {
            return (arg => originalFunc(bindValue, arg));
        }

        public static Func<T0, R> Bind2nd<T0, T1, R>(T1 bindValue, Func<T0, T1, R> originalFunc)
        {
            return (arg => originalFunc(arg, bindValue));
        }

        
        public static Func<T0, bool> Not<T0>(Func<T0, bool> originalFunc)
        {
            return (arg => !originalFunc(arg));
        }

        public static Func<T0, T1, bool> Not<T0, T1>(Func<T0, T1, bool> originalFunc)
        {
            return ((arg1, arg2) => !originalFunc(arg1, arg2));
        }

        public static Func<T0, T1, T2, bool> Not<T0, T1, T2>(Func<T0, T1, T2, bool> originalFunc)
        {
            return ((arg1, arg2, arg3) => !originalFunc(arg1, arg2, arg3));
        }

        public static Func<T0, T1, bool> And<T0, T1>(Func<T0, T1, bool> originalFunc, Func<T0, T1, bool> originalFunc2)
        {
            return ((arg1, arg2) => originalFunc(arg1, arg2) && originalFunc(arg1, arg2));
        }

        public static Func<T0, bool> And<T0>(Func<T0, bool> originalFunc, Func<T0, bool> originalFunc2)
        {
            return (arg1  => originalFunc(arg1) && originalFunc2(arg1));
        }

        public static Func<T0, T1, bool> Or<T0, T1>(Func<T0, T1, bool> originalFunc, Func<T0, T1, bool> originalFunc2)
        {
            return ((arg1, arg2) => originalFunc(arg1, arg2)  || originalFunc(arg1, arg2));
        }
        
        public static Func<T0, bool> Or<T0>(Func<T0, bool> originalFunc, Func<T0, bool> originalFunc2)
        {
            return (arg1 => originalFunc(arg1) || originalFunc2(arg1));
        }

        public static Predicate<T> ToPredicate<T>(Func<T, bool> originalFunc)
        {
            return arg => originalFunc(arg);
        }
    }
}

Share/Save/Bookmark Sunday, October 05, 2008 5:34:45 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [4]  .NET Framework | Compact .Net Framework | LINQ | Návrhové vzory


 Monday, September 08, 2008
Poslední volná místa na kurzu OOP a návrhových vzorů

Několik zájemců o veřejný kurz OOP a návrhových vzorů raději zvolilo inhouse variantu kurzu, proto bych vás rád upozornil,  že se můžete ještě nyní dodatečně hlásit na kurz OOP a návrhových vzorů.

Pozvánka s odkazy na podrobné informace o kurzu.


Share/Save/Bookmark Monday, September 08, 2008 8:58:28 AM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Analytické drobky | Kurzy UML a OOP | Návrhové vzory | Ostatní


 Sunday, May 18, 2008
Pozvánka na říjnový termín kurzu Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací

Dovolte mi, abych Vás všechny pozval na podzimní termín kurzu Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací.  Po vyhodnocení některých připomínek jsme se rozhodli změnit místo, kde se školení uskuteční.

Hotel VILLA Praha
Okrajní 1
100 00 Praha 10

Změna místa přinese účastníkům hlavně možnost zaparkovat auto přímo u hotelu a také to, že oběd (v ceně školení) dostaneme přímo v hotelu (oběd může prý obsluha donést až do učebny) a nebudeme tak nuceni trávit dlouhý čas čekáním na nějakého unuděného a pomalého pingla v poloprázdné pizzerii nebo podobném podniku s mizernou kuchyní.

Termín:

20. 10. - 22. 10. 2008

Organizační informace ke kurzu

Program kurzu

Zaregistrované ohlasy na školení :

http://blog.renestein.net/ct.ashx?id=10d7acf8-1026-43fe-b1f1-54fccb69105b&url=http%3a%2f%2fwww.jirifabian.net%2fwordpress%2f%3fp%3d157

http://blog.renestein.net/ct.ashx?id=10d7acf8-1026-43fe-b1f1-54fccb69105b&url=http%3a%2f%2fwww.rarous.net%2fclanek%2f143-skoleni-oop-uml-a-navrhovych-vzoru.aspx

 

Update: Nový ohlas

"Rene Stein pořádá opět svůj kurz o OOP - více na jeho blogu. Měl jsem možnost účastnit se školení od pana Kravala i školení pana Steina a doporučil bych spíše ten posledně jmenovaný. Je tak nějak více o praxi a z praxe. Oproti tomu kurz pana Kravala je spíše teoretický, Ale každému může vyhovovat něco jiného." Zdroj  - Martin's world


Share/Save/Bookmark Sunday, May 18, 2008 12:11:09 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Analytické drobky | Kurzy UML a OOP | Návrhové vzory | UML


 Wednesday, June 27, 2007
Pozvánka na podzimní běh kurzu Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací

Chci vás pozvat na podzimní termíny kurzu Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací.

Termíny:

24. - 26. 9. 2007
5. - 7. 11. 2007

Organizační informace ke kurzu

Program kurzu

Zaregistrované ohlasy na školení :

http://blog.renestein.net/ct.ashx?id=10d7acf8-1026-43fe-b1f1-54fccb69105b&url=http%3a%2f%2fwww.jirifabian.net%2fwordpress%2f%3fp%3d157

http://blog.renestein.net/ct.ashx?id=10d7acf8-1026-43fe-b1f1-54fccb69105b&url=http%3a%2f%2fwww.rarous.net%2fclanek%2f143-skoleni-oop-uml-a-navrhovych-vzoru.aspx

 

Omlouvám se za tento přízračně mrtvolný klid na blogu, ale kvůli neočekávaným osobním problémům nemám na blog vůbec čas. Do konce září (alespoň doufám) ale konečně změním kompletně blogovací systém a nasadím slibované wiki + fórum o  OOP a návrhových vzorech.


Share/Save/Bookmark Wednesday, June 27, 2007 9:38:11 AM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Kurzy UML a OOP | Návrhové vzory | UML


 Monday, September 11, 2006
Fórum o OOP, UML, návrhových vzorech, MDA, DSL ... - chtěli byste?

Nadpis vyjadřuje v kostce vše. Hraji si právě teď s překladem a nastavením YetAnotherForum a napadlo mě, že bych na doméně forum.renestein.net spustil fórum, kde bychom společně diskutovali o návrhu aplikací, systémovém designu, OOP, UML, Model Driven Architecture, DSL, zuřivě bychom se hádali nad best practices, vášnivě "flamovali" nad podporou OOP v různých programovacích jazycích :) nebo bychom si vyměňovali linky na zajímavé články. Pro každé větší téma by existovalo samostatné fórum.

Vím. že některá česká fóra se OOP a analýzou zabývají, ale kvůli svému neodvolatelně  finálním stavu  "mrtvé" fórum s občasnými "self" přechody, spuštěnými přijetím jedné OT zprávy s nabídkou domácích zásob viagry nějakého momentálně insolventního a celoživotně impotentního spammera, se v nich nic zajímavého neděje.

Takže - máte zájem? :) 


Share/Save/Bookmark Monday, September 11, 2006 2:55:02 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [19]  Návrhové vzory | Ostatní | UML


 Sunday, August 20, 2006
UPDATE 1.9. 2006 - Pozvání na kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací v termínu 25-27.10.

Update 1.9. 2006  - Kurz je obsazen

Kvůli velkému zájmu o zářijový termín kurzu Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací se bude stejný kurz konat také v termínu 25-27.10. 2006. Přihlásit se je opět možné zasláním emailu na adresu petra@renestein.net

Přejít na podrobné informace o kurzu včetně přihlášky na kurz


Share/Save/Bookmark Sunday, August 20, 2006 4:59:53 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Kurzy UML a OOP | Návrhové vzory | UML


 Sunday, April 09, 2006
Bázová třída pro typové kolekce v .NET Frameworku 2.0

V souvislosti s uvedením generiky v .NET Frameworku se v různých článcích dočtete, jak generika usnadní vytváření a použití typových kolekcí. To je sice pravda, ale v článku se kromě zjednodušených příkladů a frikulínských hlášek o dokonalosti .NETu 2.0 z úst (respektiva pera) excitovaných jedinců po právě dokonaném intimním styku se softwarovou emanací božstva Microsoftu :-D málokdy dovíte, jak by taková typová kolekce měla vypadat v běžné aplikaci.

Proč nevyhovuje obyčejné použití generického typu List? (deklarace typové kolekce objednávek ve tvaru List<Order> orderCollection = new List<Order>()).

  • Jednou z dobrých praktik u generik je co nejvíce před uživateli (dalšími vývojáři) skrývat informaci, že pracují s generickým typem. Ač mně syntaxe pro práci s generickými typy připadá intuitivní, nemusí si to myslet všichni, a mnoho vývojářů stále asi raději používá důvěrně známý kód OrderCollection orderCollection = new OrderCollection() místo výše zmíněného kódu List<Order> orderCollection = new List<Order>()).  Tento požadavek by ale List<T> splnil  - typová kolekce může být potomkem List<T>.
  • Ve třídě List nejsou metody Add a Remove virtuální. To znamená, že nemůžete po přidání nebo odebrání položky z/do kolekce vyvolávat vlastní události. A to je problém, protože po přidání/odebrání položek z kolekce můžeme chtít nastavit/zrušit rodiče nebo přepočítát sumární hodnoty za položky v kolekci apod.

Bázová třída pro všechny kolekce, kterou používám, je otevřeným generickým typem a jejím předkem je třída Collection<T> z jmenného prostoru System.Collections.ObjectModel. Třída Collection<T> nabízí virtuální chráněné metody InsertItem a RemoveItem, ve kterých můžete vyvolávat potřebné události. Jestliže používate návrhový vzor Layer SuperType a máte tedy jednu bázovou třídu pro všechny business objekty (BusinessObjectBase), je vhodné, aby bázová třída pro kolekce kladla na generický typ T omezení, že musí být potomkem třídy BusinessObjectBase. Omezení slouží k tomu, abyste ve svých kolekcích mohli intuitivně používat všechny atributy a metody deklarované na úrovni společného předka BusinessObjectBase.

Kód kolekce:

   public class BusinessCollectionBase<T> : Collection<T> 
                                        where T : BusinessObjectBase
    {
        #region Events
        public event EventHandler<CollectionChangeEventArgs> ItemAdded;
        public event EventHandler<CollectionChangeEventArgs> ItemRemoved;
        #endregion Events
        #region Protected methods
        /// <summary>
        /// Přidání položky do kolekce
        /// </summary>
        /// <param name="index">Index položky</param>
        /// <param name="item">Vkládaná položka</param>
        protected override void InsertItem(int index, T item)
        {
            base.InsertItem(index, item);
            OnItemAdded(new CollectionChangeEventArgs(item));
        }

        /// <summary>
        /// Přidání položky do kolekce
        /// </summary>
        /// <param name="index">Index položky</param>        
        protected override void RemoveItem(int index)
        {
            T item = this[index];
            base.RemoveItem(index);
            OnItemRemoved(new CollectionChangeEventArgs(item));
        }

        /// <summary>
        /// Metoda odpovědná za vyvolání události ItemRemoved
        /// </summary>
        /// <param name="e">Argumenty události</param>
        protected void OnItemRemoved(CollectionChangeEventArgs e)
        {
            if (ItemRemoved != null)
            {
                ItemRemoved(this, e);
            }
            
        }
        
        /// <summary>
        /// Metoda odpovědná za vyvolání události ItemAdded
        /// </summary>
        /// <param name="e"></param>
        protected void OnItemAdded(CollectionChangeEventArgs e)
        {
            if (ItemAdded != null)
            {
                ItemAdded(this, e);
            }
         
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual T FindById(Guid id)
        {
            List<T> mylist = (List<T>) Items;
            T objectMeetsCriteria = null;
            objectMeetsCriteria = mylist.Find(delegate(T iterObject)
                                  {
                                      if (iterObject.Id == id)
                                      {
                                          return true;
                                      }
                                      else
                                      {
                                          return false;
                                      }
                                                                
                                  });

            return objectMeetsCriteria;
        }

        /// <summary>
        /// Nalezení všech objektů splňujících zadanou podmínku
        /// </summary>
        /// <param name="criteria">Podmínka výběru</param>
        /// <returns>List s objekty, které splňují zadanou podmínku</returns
        public virtual List<T> Find(Predicate<T> criteria)
        {
            List<T> mylist = (List<T>) Items;           
            return (mylist.FindAll(criteria));
            
        }
        
        /// <summary>
        /// Spuštění akce nad všemi elementy v kolekci
        /// </summary>
        /// <param name="action">Akce, která se má provést</param>
        public virtual void ForEach(Action<T> action)
        {
            List<T> mylist = (List<T>)Items;
            mylist.ForEach(action);            
        }
    
        #endregion Protected methods
    }
}

Jak vidíme:

  1. Třída BusinessCollectionBase je potomkem třídy Collection<T> a vyžaduje, aby typ T byl vždy potomkem BusinessObjectBase. Motivace pro toto rozhodnutí jspu popsány výše.
  2. Nadeklarovali jsme dvě události ItemAdded a ItemRemoved, které jsou vyvolávany v přepsaných metodách InsertItem (ItemAdded) a RemoveItem (ItemRemoved). Pokud bych měl zájem, mohu jednoduše přidat i události vyvolávané před přidáním/odebráním položky z kolekce.
  3. Do rozhraní BusinessCollectionBase jsem také přidal několik zajímavých metod.
    1. Metoda FindById nalezne podle předaného Id (unikátní identifikátor instance) objekt v kolekci. V této metodě opět používáme nové konstrukce z .Net Frameworku 2.0. Implementační objekt kolekce (starý známý List<T> ) vystavuje metodu Find, která očekává generického delegáta Predicate z jmenného prostoru System.
      public delegate bool Predicate( T obj);
      Delegát Predicate je "ukazatelem" na metodu, která očekává jeden generický argument T a vrací true nebo false. Delegát Predicate tedy zastupuje metodu s podmínkou, která je pro předaný argument obj pravdivá nebo nepravdivá. My pro vytvoření podmínky použijeme anonymní metodu, která vrátí true pouze tehdy, když se Id objektu v kolekci shoduje s předaným Id. Atribut Id u generického typu T kolekce můžeme používat právě proto, že jsme zavedli pro typ T omezení (musíš být potomkem  BusinessObjectBase) a atribut Id je deklarován ve třídě BusinessOBjectBase.
    2. Pro pokročilejší operace s elementy kolekce jsme z objektu List<T> zveřejnili metody FindAll A ForEach. Metoda FindAll podle předané podmínky (delegát Predicate) nalezne a vrátí všechny objekty, které jí vyhovují. Metoda ForEach spustí pro všchny elementy v kolekci "akci - činnost" implementovanou v metodě, na níž "ukazuje" další užitečný delegát Action<T>.

      public delegate void Action<T> ( T obj);

      Když tedy budete chtít všechny objekty v kolekci zrušit, místo psaní cyklu foreach napíšete kód podobný tomuto:

      myCol.ForEach(delegate(OrderItem item)
      
                  {
      
                       item.Discard();
      
                  });
      

Vytváření vlastních typových kolekcí je jednoduché:

    /// <summary>
    /// Kolekce objektů OrderItem
    /// </summary>
    public class OrderItemCollection : BusinessCollectionBase<OrderItem>
    {

    }
 

Pro úplnost sem dávám triviální kód třídy pro argumenty události CollectionChanged.

    /// <summary>
    /// Objekt v kolekci
    /// </summary>
    public class CollectionChangeEventArgs : EventArgs
    {
        #region Private variables
        private BusinessObjectBase m_collectionObject;
        #endregion Private variables
        
        #region Constructors
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="collectionObjekt">Objekt v kolekci, kterého se událost týká</param>
        public CollectionChangeEventArgs(BusinessObjectBase collectionObject)
        {
            BasicValidations.AssertNotNull(collectionObject, "collectionObject");
            m_collectionObject = collectionObject;
        }

        /// <summary>
        /// Objekt v kolekci, kterého se událost týká
        /// </summary>
        public BusinessObjectBase CollectionObject
        {
            get
            {
                return m_collectionObject;
            }
        }
        #endregion Constructors
    }
Související články:

Bázová třída pro business objekty - návrhový vzor Layer Supertype
Cachování řádků z databáze pro business objekty - třída DataCacheHelper
Ukázka použití třídy BusinessObjectBase


Share/Save/Bookmark Sunday, April 09, 2006 2:34:33 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [7]  .NET Framework | Compact .Net Framework | Návrhové vzory


 Sunday, March 05, 2006
Termíny kurzů o OOP a UML, partnerství s EIITE, InHouse školení

UPDATE:

Podrobné a aktuální informace o dalších termínech školení a nabídce dalších služeb naleznete nyní zde.


 

Jak jsem již naznačil v předchozím spotu, uzavřel jsem "strategické partnerství" s novým školícím střediskem - se společností EIITE. Mé kurzy budou pořádány pod hlavičkou Skilldrive.

Všechny kurzy se budou konat v Praze.

Přesná adresa:

EIITE Praha
Karlovo nám. 17
120 00 Praha 2

 

Termíny kurzů:

  • Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací - 3-5.5 2006 (každý den od 9:00 do 16:00). Program a další informace o kurzu naleznete v předchozím spotu. Přihlášky na kurz je možné stále posílat na adresu petra@renestein.net.
    Kurz je obsazen: Pokud máte stále  zájem o kurz, napište nám prosím opět na adresu petra@renestein.net a my Vás budeme informovat ihned po vyhlášení  dalšího termínu školení. Je také možné stále objednávat InHouse školení (viz níže).
  • Kurz UML pořádaný a administrativně zajišťovaný společností EIITE - 28-29.8. 2006. Přihlášení na kurz.

InHouse školení

Pokud máte zájem o inhouse školení zaměřené na OOP, návrhové vzory, UML, .NET Framework, Compact .Net Framework nebo chcete vytvořit kompletní systémový design aplikace včetně naprogramování klíčových částí, napiště prosím svůj požadavek také na adresu petra@renestein.net nebo nás kontaktujte na telefonním čísle +420 603 266 732. Omezení pro inhouse školení, o kterých jsem zde psal dříve, již neplatí!


Share/Save/Bookmark Sunday, March 05, 2006 12:36:23 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Kurzy UML a OOP | Návrhové vzory | UML


 Sunday, February 26, 2006
Několik informací ke kurzům UML a OOP

Takže předběžně ke kurzům UML a OOP, protože se mi nechce psát stále stejné věci každému zvlášť do mailu. V souvislosti se změnou zaměstnání se mění i některé věci ke kurzům.

  1. Kurzy OOP a UML již nebudou pořádány ve školícím středisku DIGI TRADE, ale ve velmi pěkných prostorách jiné společnosti, s níž jsem uzavřel "strategické partnerství" a jejíž jméno oznámím příští týden.
  2. První termín (pravděpodobně konec dubna/začátek května) patří kurzu Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací., na kterém již mnozí z vás byli. Pro připomenutí níže vkládám program kurzu. Pokud máte již nyní zájem předběžně si zarezervovat místo na kurzu, pošlete prosím email na adresu petra@renestein.net. Cena kurzu je 13 000 Kč.
  3. Dále cca za měsíc zveřejním podrobný program a termín dalšího mého školení s pracovním podtitulem "Design aplikací a návrhové vzory prakticky  - od chaosu k řádu".
  4. S novým školícím střediskem budeme také pořádat "jednodušší" (přesněji řečeno jinak zaměřený a více na základní znalosti orientovaný) kurz, uvádějící posluchače prakticky do světa UML (2.0). Podrobnosti a termín zveřejním do 14 dnů.
  5. Fungovat to bude takto - první dva kurzy je po administrativní stránce zcela na mě, kurz o UML bude po admistrativní stránce plně zajišťovat nové školící středisko. I program kurzu o UML jsem samozřejmě připravil i já, nikdy bych nemohl školit nějaké "předžvýkané" řečičky někoho jiného. Uvádím to zde jen proto, aby vás nemátlo, když se u některých kurzů objeví informace, že máte přihlášky posílat mně (respektive mé ženě) a u jiných budete přihlášky směřovat rovnou na nové školící středisko. Kurzy se ale budou vždy konat na stejném místě.
  6. Omlouvám se těm, kdo absolvovali první kurz a čekají na již poměrně dlouho slibované uvolnění studijních materiálů. Prozatím jsem nenašel čas (respektive vždy jsem našel zajímavější práci ;) , než je "vyčištění" materiálů k veřejnému použití) a také se mi nechce kvůli špatným zkušenostem uvolňovat materiály před konáním dalších kurzů, protože jsem se už v reálu setkal s tím, jak materiály neurčené široké veřejnosti začnou po internetu rychle kolovat, po čemž opravdu nijak netoužím. :(

1. den

  1. Přivítání účastníků kurzu.
  2. Úvodní informace o zaměření a organizaci kurzu.
  3. Základní pojmy OOP a UML..
  4. Mýty o OOP a UML.
  5. Vysvětlení rozdílů mezi business analýzou, systémovým designem a implementací aplikace na konkrétní platformě.
    1. Světlo v temnotách – Model Driven Architecture (MDA)
  6. Základní architektura a rozvrstvení aplikace.
  7. Statický pohled na systém – vytváříme základní diagram tříd a ověřujeme, že jsou v něm zaneseny všechny informace, jež jsou nám známy z případů užití.
    1. Zvolení složitosti diagramu tříd. Potřebujeme vždy flexibilní doménový model?
    2. Zapouzdření objektů, polymorfismus, návrh metod.

                                                               i.      Důležitost principů kovariance a kontravariance.

                                                             ii.      Různé typy soudržnosti metod.

                                                            iii.      Rozhodnutí o typu viditelnosti u každého člena třídy.

                                                           iv.      Jaké konstruktory by měl nabízet každý objekt z problémové domény? Jak určit vlastnosti pouze pro čtení.

                                                             v.      Ověření bezpečného chování třídy vůči potenciálním klientům.

    1. Precizní definice vztahů mezi třídami. Asociace, kompozice, agregace, závislost, realizace, generalizace.
    2. Vysvětlení rozdílů mezi abstraktní třídou a rozhraním (interface).

                                                               i.      Vztah mezi typem a podtypem.

                                                             ii.      Rozpoznání primárního účelu (hlavního smyslu) třídy i jejich sekundárních odpovědností vynucených vztahy s objekty z různých vrstev.

  1. Praktický příklad - ukázka implementace vzorových vztahů mezi objekty, perzistence objektů z problémové domény a zobrazování dat.  (jazyk C#)
    1. Separace kódu pro ukládání a obnovení objektů z perzistentního úložiště v samostatné vrstvě.
    2. Jak zajistíme, že v paměti počítače existuje nanejvýš jedna instance objektu se stejnou identitou.
    3. Ukázky různých způsobů mapování agregace, kompozice, generalizace a asociace do databáze.
    4. Zajištění existence maximálně jedné instance objektu v systému.
    5. Efektivní ukládání a nahrávání kolekcí.
    6. Jak se slučuje objektový přístup a přímé použití DataSetu (recordsetu) v uživatelském rozhraní?
  2. Odpovědi na dotazy frekventantů kurzu.

2. den

  1. Vysvětlení pojmu návrhový vzor.
  2. Kdy byste měli používat návrhové vzory?
  3. Základní vzory (GoF vzory)
    1. Vzory pro řízení vzniku objektů.
    2. Strukturální vzory.
    3. Vzory pro chování objektů.
  4. Začlenění návrhového vzoru do designu aplikace. Kreativní aplikace vzorů.
  5. Kompozice vzorů do vyšších sémantických celků.
  6. Příklady odvozených návrhových vzorů často používaných při designu informačního systému.
  7. Kdy byste neměli používat návrhové vzory?
  8. Příklad - ukázky implementace složitějších vzorů. (jazyk C#).
  9. Odpovědi na dotazy frekventantů kurzu.

3. den

  1. Typické problémy při modelování informačního systému a jejich řešení.
  2. Modelování  složitých organizačních struktur.
  3. Výhody vytváření fasád (Facade) pro aplikace s více než jedním typem uživatelského rozhraní (lehký klient, těžký klient).
  4. Evidence kompletní historie objektu.
  5. Aplikační role a práva uživatelů.
  6. Vytvoření flexibilního systému, jehož chování je změněno bez rekompilace aplikace.
  7. Příklad – ukázky řešení problémů při modelování informačního systému. (jazyk C#).
  8. Odpovědi na dotazy frekventantů kurzu.
  9. Ukončení kurzu.

Share/Save/Bookmark Sunday, February 26, 2006 8:27:49 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Kurzy UML a OOP | Návrhové vzory | UML


 Sunday, February 05, 2006
Ukázka použití třídy BusinessObjectBase

Máme-li napsanou třídu BusinessObjectBase, která hraje roli společného předka všech objektů v business vrstvě všech našich aplikací, je čas ukázat, jak vypadají typičtí potomci.



Předchozí spoty

Bázová třída pro business objekty - návrhový vzor Layer Supertype
Cachování řádků z databáze pro business objekty - třída DataCacheHelper

Zobrazit kód

Třída Order reprezentuje v našem systému objednávku. U objednávky nás zajímá, jakému patří zákazníkovi (vlastnost Customer), rozhodli jsme se, že budeme vyžadovat u každé instance objednávky popisek (vlastnost Description) a samořejmě samotná objednávka je složena z položek objednávky (kolekce Items). Položkou objednávky budeme rozumět třídu, v níž je uloženo, jaké zboží a kolik kusů si zákazník objednal a celková cena položky (kusová cena objednaného zboží * počet). Celková cena objednávky je sumou cen jednotlivých položek. Pro jednoduchost vidíte v kódu zatím jen třídu Objednávka.

  1. Třída Objednávka má dva konstruktory. Prvni konstruktor, přijímající popisek objednávky a zákazníka, vyvolává bezparamerický konstruktor třídy BusinessObjectBase. Jak jsem psal v předchozím spotu, bezparametrický konstruktor slouží k sestrojení objektů bez obrazu v databázi. Chcete-li založit novou objednávku, stačí když si uložíte odkazy na objekty předané v konstruktoru a zbytek práce delegujete na BusinessObjectBase, která nastaví všechny potřebné příznaky nutné pro práci s objektem bez perzistence. Druhý konstruktor přijímá Id (unikátní identifikátor) objednávky, která je uložena v databázi. Jeho tělo je prázdné, veškerou činnost provádí BusinessObjectBase - nastavení příznaků a uložení Id pro pozdější nahrání objektu z databáze. Objekt je po vykonání konstruktoru "prázdnou schránkou", duchem (GHOST), který načte svá data z databáze teprve tehdy, když použijeme některou metodu nebo vlastnost pracující s instančními a perzistovanými proměnnými. Z hlediska uživatele naší třídy (myšleno programátora)  je ale vše ve starých kolejích - rozhraní objektu bude reagovat tak, jak očekává,  a existenci pozdní incializace objektu (Lazy Load) může ignorovat. Poslední větu si přečtěte ještě jednou a tiše s k ní přidejte pravidlo: Nikdy nesmím nutit uživatele svých tříd volat nějaké speciální "Init" metody předtím, než začnou s třídou pracovat. Jedinou legální "Init" metodou je konstruktor.
  2.  Jak k pozdní incializaci a tedy nahrání dat objektu dojde? Vlastnosti, jejichž hodnoty jsou uložené v databázi (Id zákazníka jako klíč v tabulce Objednávek a popisek (Description) objednávky) mají na prvním řádku svých get/set přístupových metod volání metody TriggerLoad. Jak víme, jde o volání metody z BusinessObjectBase, která sama rozhoduje, zda již byla data objektu nahrána z databáze. Pokud data nebyla nahrána, třída BusinessObjectBase řídí scénář nahrávání objektu v metodě Load. Jestliže jsou data pro objednávku v třídě DataCacheHelper, je volána metoda DoInternalLoad(DataRow row), která načte data objednávky z předaného řádku. Když v třídě DataCacheHelper data pro aktuální instanci nemáme, BusinessObjectBase volá variantu metody DoInternalLoad bez argumentů. Metoda DoInternalLoad vyzvedne řádek z databáze s využitím databázové komponenty (což je Singleton nebo Thread specific storage - jednoduše třída zapoudřující  API pro práci s datovým zdrojem) a tento řádek předá své stejně nazvané sestřičce, o níž byla řeč výše.
    Důležité je, že celý scénář nahrávání řídí bázová třída - v odvozené třídě jen v přesně vymezených bodech scénáře "dosazuje" třída Order své vlastní specifické chování a používá své speciální atributy.
  3. V set přístupových metodách u vlastností využíváme metodu CheckEquals z BusinessObjectBase. Jak už víte z minulého spotu, metoda CheckEquals zkontroluje rovnost dvou objektů a podle toho nastaví příznak IsDirty - byl objekt změněn proti datům v databázi a musí tedy dojít k jeho uložení?
  4. Jak je to nahráním položek objednávky  - vlastnost Items? Často se stává, že potřebujete pracovat se samotným objektem, ale je zbytečné při nahrání dat objektu  z databáze ihned plnit i všechny jeho kolekce. Kolekce Items je naplněna při prvním přístupu ke kolekci  - příznak, zda byla kolekce nahrána nese pomocná instanční proměnná m_itemsLoaded. Jestliže kolekce naplněna nebyla, je volána privátní metoda loadItems, v níž s pomocí databázové komponenty vyzvedneme z databáte všechny položky objednávky - filtrem, který je použit v SQL dotazu je samozřejmě Id objednávky. Poté projdeme vrácenou tabulku a všechny řádky uložíme do třídy DataCacheHelper. Zde přepokládáme, že metoda DbComponent.Instance.OrderItem_GetByOrderId, vrací z databáze všechna data potřebná pro obnovení objektu z databáze, nejen jejich Id. To je běžná praxe, pokud máte uloženou proceduru, která vybírá záznamy z jedné tabulky (pro jednu třídu) podle různých podmínek. Důležité je, že za pomoci identitní mapy (Identity Map) sestrojíme objekty - "duchy" - OrderItem, kterým předáme jejich Id,  a třída BusinessObjectBase již zajistí, že když instance OrderItem budou chtít nahrát svá data z databáze, tak jí bude vrácen řádek uložený v instanci třídy DataCacheHeper. To znamená, že třída OrderItem nebude zbytečně zatěžovat databázi duplicitními dotazy a přitom kontrola na existenci cachovaného řádku pro instance jakékoli třídy je součástí společného předka BusinessObjectBase a "nezasviníme" si stejným kódem celou business vrstvu.
  5. Metoda Order také přepisuje metodu DoInternalSave, v níž uloží své atributy do databáze. Jak uvidíme dále, metodě Order_Update předáváme hodnotu zděděného a bázovou třídou spravovaného příznaku IsNew - tento příznak metoda interně použije k rozhodnutí, zda provede SQL příkaz INSERT nebo UPDATE1. Opět - po volání metody Save uživatelem jen třída BusinessObjectBase jakožto finální instance rozhoduje, zda je potřeba data ukládat a zda je tedy vubec nutné a účelné volat metodu DoInternalSave. Opět vidíte čistý řez mezi kontrolou, kdy je nutné objekt uložit (třída BusinessObjectBase),  a samotným ukládáním (třída Order i další potomci BusinessObjectBase). Kromě uložení vlastních hodnot odpovídá třída za uložení agregovaných objektů OrderItem. Proto v metodě DoInternalSave nejprve zkontroluje, jestli byly objekty v kolekci nahrány (příznak m_itemsLoaded) a pokud ano, tak jen zavolá pomocnou metodu SaveCollection, která je implementována, jak jinak že;), v třídě BusinessObjectBase.

I když z příkladu byste měli začít tušit, proč je BusinessObjectBase tak výhodná, stále neřešíme některé problémy.

  1. V příkladu není vůbec řešeno odebírání a přidávání položek do kolekce - to znamená nastavování/zrušení "rodiče" u agregovaných objektů. Nijak jsme neřešili m:n relace a odpovědnost za zakládání/rušení záznamů ve vazebních tabulkách. Myslíte, že i zde půjde využít třída BusinesObjectBase? ;)
  2. Co když si budeme chtít u některých potomků  BusinessObjectBase "vynutit" jiné chování - třeba zamezit použití DataCacheHelperu?
  3. A co transakce? V našem příkladu zatím nijak neřešíme ukládání objektů v transakci, což bychom měli, protože určitě nechceme mít v systému objednávky s polovinou objednaných položek. Jaký objekt má spouštět a řídit transakci? Kdo musí ošetřit chyby vzniklé při ukládání objektu?

To be continued... :)

Poznámky:

  1. Jsem si vědom střídavé soudržnosti metody související s "přepínačem" IsNew . V dalších dílech bude vysvětleno, proč střídavá soudržnost zrovna u této metody nevadí.

Share/Save/Bookmark Sunday, February 05, 2006 2:44:36 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [7]  Návrhové vzory


 Sunday, August 21, 2005
Bázová třída pro business objekty - návrhový vzor Layer Supertype

Na konci spotu, v němž byla vytvořena třída DataCacheHelper, jsem slíbil, že ukážu, jak si vynutit použití objektu DataCacheHelper všemi třídami v business vrstvě. K tomu nám poslouží vzor Layer SuperType, jehož definice se dá parafrázovat takto: zaveďme společného předka pro všechny třídy jedné vrstvy aplikace, v němž zapoudříme společný kód a sdílené chování.

Nyní si popíšeme rozhraní a implementaci vzorového předka pro business objekty. Zobrazit kód. (C#)

Abstraktní třída BusinessObjectBase obsahuje dva konstruktory. Bezparameterický konstruktor je potomky vyvoláván při zakládání nové instance bez obrazu v databázi ("bez platného - perzistentního id). V tomto konstruktoru nastavíme, že objekt není změněn vůči datům v databázi (m_isDirty = false), přidělíme mu dočasné id uložené v konstantě NO_ID a voláním privátních metod setNew a setLoaded označíme objekt jako "nový - bez obrazu v databázi" a současně "není třeba nahrávat data z databáze". U každého nového objektu se také předpokládá, že jde o aktivní objekt - tedy o objekt, který nemá příznak smazán (Discarded) ani neaktivní (InActive) - viz enumerace ObjectState na konci výpisu.

Druhý konstruktor, který přijímá argument id, používají odvozené objekty, jestliže již byly dříve uloženy do databáze. Opět nastavíme příznak, že objekt není změněn vůči datům v databázi (m_isDirty), dále nastavíme proměnnou m_isLoaded na false, což je příznak, který signalizuje, že data objektu ještě nebyl nahrána z databáze, a uložíme si id objektu.

Veřejná vlastnost IsDirty vrátí true, jestliže byl objekt změněn vůči datům v databázi, vlastnost IsNew vrátí true, jestliže se jedná o nový objekt bez perzistence - čeká na "Insert".

Vlastnost IsDeleted má hodnotu true, když již byla data objektu v databázi smazána příkazem Delete (objekt jen "dožívá" v business vrstvě) nebo když u objektů, jejichž data se například kvůli zachování referenční integrity fyzicky nemažou, byl do databáze zapsán stav Discarded.

Vlastnost Id vrací unikátní identifikátor objektu, jestliže byl objekt uložen do databáze, jinak vrací konstantu NO_VALUE.

Vlastnost ObjectState vrací stav objektu s využitím hodnot enumerace ObjectState (aktivní objekt, dočasně neaktivní objekt, smazaný objekt).

Metoda Load je pro nás zajímavá hlavně tím, že si vynutíme použití třídy DataCacheHelper. Jedná se metodu se šablonou (vzor Template method), která nejprve zkontroluje, jestli jde o objekt, který již byl nahrán a/nebo který byl již smazán - pokud je jedna z těchto podmínek pravdivá, metoda Load ihned ukončí svoji činnost. Když jsou všechny předběžné podmínky pro vyvolání metody Load splněny, metoda se pokusí vyzvednout dříve uložený řádek s daty pro svou instanci z třídy DataCacheHelper. Jestliže řádek existuje, pak je vyvolána varianta metody DoInternaLoad přijímající odkaz na řádek, jinak je vyvolána metoda DoInternalLoad, která musí řádek z databáze teprve získat. Metody DoInternalLoad jsou chráněné a virtuální a za jejich implementaci odpovídají odvozené třídy - třída BusinessObjectBase dbá na dodržení základní kostry algoritmu, ale implementaci jednotlivých kroků aloritmu ponechává (musí ponechat) na podtřídách, což je hlavní idea vzoru Template Method. Po úspěšném nahrání objektu je volána metoda setLoaded, která označí objekt jako nahraný (IsLoaded bude nyní vracet true).

Poznámka: V metodě Load se zatím předpokládá, že data objektu potřebujeme nahrát pouze jednou a metoda tedy neumožňuje aktualizaci dat objektu z databáze po prvním nahrání. Jak aktualizaci dat z databáze odvozeným třídám umožníme, si ukážeme v dalším pokračování.

Metoda Save neumožní uložení objektu, jestliže jde o nenahraný objekt ("není co ukládat, data nemohla být změněna"), jestliže jde o vymazaný objekt, nebo pokud objekt nebyl změněn a současně nejde o nový objekt. Metoda tedy již na úrovni předka ošetří, že nebudou zbytečně ukládány objekty, jejichž data byla nahrána, ale uživatel je nijak nezměnil. Metoda pouze volá chráněnou virtuální metodu DoInternalSave a jak asi ti chytřejší z vás tuší, jedná se o další příklad vzoru Template Method. Voláním privátní metody setClear po úspěšném uložení objektu nastavíme na false příznak "změněn" (IsDirty) a "nový objekt" (IsNew). Jestliže byl ukládán objekt, u nějž bylo vyžádáno smazání (ObjectState=ObjectState.Discarded), je nastaven příznak IsDeleted voláním chráněné metody SetDeleted na true.

Metoda Discard slouží ke zrušení objektu, přesněji řečeno k převedení objektu do stavu ObjectState.Discarded - pokud je objekt v tomto stavu a dojde k uložení objektu, metoda DoInternalSave buď záznam v databázi fyzicky vymaže (DELETE) nebo do databáze uloží stav ObjectState.Discarded (UPDATE). Metoda Discard pouze zkontroluje, zda nepracujeme nad smazaným objektem (IsDeleted=true), u nějž opakované rušení nemá smysl, a jestliže tomu tak není, nastaví objektu stav ObjectState.Discarded a označí objekt voláním metody SetDirty jako změněný.

Chráněnou statickou metodu SaveCollection používají odvozené třídy pro ukládání objektů ve svých kolekcích. (vztah agregace nebo kompozice). Výhodou je, že v odvozených třídách nemusí být stále stejný a otravný cyklus foreach, který se liší jen typem iterovaného objektu.

Metody DoInternalLoad a DoInternalSave jsme již zmiňovaly  - jsou to "sloty" používané odvozenými třídami k zavedení vlastní aplikační logiky do schématu vysokoúrovňového scénáře. Bázovou implementaci metody DoInternalLoad přijímající řádek s daty objektu mohou zavolat odvozené třídy, jestliže chtějí nahrát stav objektu (ObjectState) bez zbytečné duplikace kódu - obnovení a ukládání hodnot vlastních specifických vlastností je samozřejmě plně v režii odvozených tříd. Metoda DoInternalLoad bez argumentů ani metoda DoInternalSave nejsou abstraktní, i když by abstraktní být mohly - v business vrstvě ale občas máme třídy, u nichž nemusíme (nechceme) psát perzistenci a proto mají metody v bázové třídě prázdné tělo, abychom nebyli nuceni zcela zbytečně ve více odvozených třídách poskytovat symbolické "prázdné implementace".

Metodu Init mohou odvozené třídy použít pro vlastní inicializační kód (vytváření kolekcí a přihlašování jejich událostí atd.) - příklad uvidíte v pokračování spotu.

Metoda SetDirty nastavuje příznak "změněn oproti datům v databázi" - příznak nemá význam pro objekty pro objekty bez perzistence a pro smazané objekty.

Metoda CheckEquals porovná předané objekty - jestliže se objekty nerovnají, je objekt označen jako změněný. Jak asi tušíte, metodu volají odvozené třídy v set sekci svých vlastností (CheckEquals(oldValue, newValue)) a jsou tak zbaveni nutnosti reagovat na výsledek porovnání a ukládat si vždy každá sama za sebe příznak "jsem změněn". Když uživatel zmáčkne tlačítko uložit na formuláři a my data z formuláře přeneseme do objektu, metoda CheckEquals zajistí, že objekt bude označen jako změněný jen tehdy, pokud uživatel opravdu nějaké změny na formuláři provedl (třeba vybral jinou hodnotu v listboxu). Výhody jsou zřejmé, kód na formuláři je stále týž - pouze "tupě" přenáší data ze vstupních prvků formuláře do vlastností business objektu a ten si již potutelně a sám na úrovni předka ošetří, zda nově předané hodnoty vyžadují uložení, nebo ne, což může nastat, když uživatel z pouhého rozmaru klikne na tlačítko uložit a přitom si detail objektu celou dobu jen prohlíží.

Metoda TriggerLoad iniciuje nahrání objektu, jestliže objekt ještě nebyl nahrán, nejde o nový objekt a také nesmí jít o smazaný objekt. Metodu volají odvozené třídy při přístupu k jakékoli vlastnosti nebo metodě objektu (vzory Lazy Load, Ghost).

Význam dalších chráněných a privátních metod byl již myslím dostatečně osvětlen v předchozím textu při výkladu veřejného rozhrani, takže se nebudu opakovat.

Rozhraní IPersistable vyjadřuje minimální nárok, který musejí splňovat všechny objekty ukládající a obnovující svá data z/do perzistentního úložiště - výhodnou výchozí a závaznou osnovu realizace rozhraní poskytuje třída BusinessObjectBase.

Z enumarace ObjectState pro vyjádření základních stavů objektu prozatím nepoužívame stav InActive - význam ostatních stavů by již vám měl být po přečtení spotu dostatečně zřejmý.

Příště si ukážeme vzorovou implementaci třídy, která dědí z BusinessObjectBase.


Share/Save/Bookmark Sunday, August 21, 2005 9:29:28 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [1]  .NET Framework | Návrhové vzory


 Sunday, August 07, 2005
Cachování řádků z databáze pro business objekty - třída DataCacheHelper

Při psaní business vrstvy libovolné aplikace jste jistě narazili na následující problém, který můžeme demonstrovat na profláknutém příkladu s objednávkami a jejich položkami. Pod položkou rozumíme instanci třídy, ve které jsou uloženy informace o vybraném produktu, počtu objednaných kusů produktu a celkové ceně. Objednávka a položka objednávky jsou třídy, které jste už určitě psali tolikrát, že nemáte rádi ani jejich názvy ;)

Každá objednávka má 0..n položek a vyžádáme-li si konkrétní položku z kolekce všech položek (Items) u objednávky, která již byla uložena do databáze, musí dojít nejprve k nahrání kolekce. Je jedno, zda položky nahráváte ihned privátní metodou nazvanou třeba loadItems volanou z konstruktoru třídy Objednávka, nebo zda (a lépe) používáte zpožděné nahrávání, kdy kolekce Items je naplněna stejnou metodou teprve po prvním přístupu a konstruktory všech business objektů pouze uloží předané unikátní identifikátory (Id) a naplní své vlastnosti uloženými daty (kromě kolekcí) až po prvním přístupu k nějaké vlastnosti (kombinace vzorů Lazy Load a Ghost). Na přístupu k plnění dat objektů nezáleží, jen vždy musíte garantovat, že uživatel objektu musí  přistupovat k datům objektu, aniž by si byl vědom provedených výkonnostních optimalizací - interní impementace je i zde nedotknutelné privatissimum objektu.

Jak zjistíte v metodě loadItems, jaké položky objednávka (třída Order obsahuje? Přes datovou vrstvu spustíte dotaz, která vrátí všechny záznamy s daty pro všechny položky objednávky - parametrem dotazu je samozřejmě Id objednávky. Kruciálním problémem je ale vytvoření položek objednávky (třída OrderItem) z vrácených záznamů. Jaké máme možnosti?

  1. Vytvoříme novou instanci OrderItem a předáme jí do konstruktoru její Id. Výhodou je, že třída Order není s třídou OrderItem nijak svázána a pouze jí předává její identifikátor. Velkou nevýhodou ale je to, že object OrderItem při obnovování svých dat z databáze bude znovu posílat dotaz do databáze "dej mi data pro mé Id", což nebude nijak efektivní přístup zvláště, když v objednávce budou desítky nebo stovky položek a každá z nich s opulentní rozhazovačností pošle svůj dotaz do databáze. Navíc je to zcela zbytečné, protože data již byla vyzvednuta v metodě loadItems, ale "jen" nebyly objektu OrderItem předána.
  2. V metodě loadItems přečteme záznamy položek objednávky, vyzvedneme data ze všech sloupců a pro každou položku vyvoláme konstruktor, který akceptuje všechny vlastnosti položky. Tedy kromě Id předáme i celkovou cenu, počet kusů atd. Sice jsme se již zbavili opakovaného dotazování do databáze položkami objednávky, ale objevily se další zásadní nevýhody. Třída objednávka je zatížena zbytečnou znalostí rozhraní (konstruktoru) třídy OrderItem a při změně třídy OrderItem, jakými jsou přidání nebo odebrání vlastnosti, budeme muset provést i změny ve třídě Order. Při tomto řešení jsme se právě vydali na strastiplnou a rozháranou životní cestu,  nazývanou moderními mudrci ze softwarových pousteven ve svých písemných testamentech "Maintenance Nightmare". Stejný problém budeme mít při plnění vlastností OrderItem, navíc bychom u každé vlastnosti museli mít i set přístupovou metodu, z čehož by se měl všem autokritickým vývojářům obracet žaludek.
  3. Třídě OrderItem přidáme konstruktor, který přijímá záznam z databáze - v případě .Net Frameworku tedy objekt DataRow. Tohle řešení sejme povinnost nést břemeno znalosti rozhraní třídy OrderItem z třídy Order, ale zanechá nás s třídami, jejichž veřejné (nebo minimálně "internal") rozhraní je zapráskáno objekty z nižších vrstev. Proč by třída měla ukazovat, že je závislá na objektech DataRow, jejichž hlavní doménou je databázová vrstva? Perzistence je u business objektů nutné zlo, ne alfou a omegou a hlavním důvodem jejich existence. Bohužel, i v mnoha složitých projektech jsou stále k vidění jen truchlivé krabičky s daty z databáze a metodami Load a Save doplněné o honosnou etiketu "business vrstva" od nějakého rekvalifikovaného outsidera s kriplovsky laděným elánem Horsta Fuchse, co se v pomatení smyslů rozhodl pomoci saturovat deficit pracovních sil na trhu s vývojáři.

Než přejdu ke cachování dat, jen drobná poznámka, abych předešel dotazům. Metoda loadItems může poslat dotaz, který vrátí jen id položek objednávky, takže se data na klienta netahají dvakrát a můj problém pak působí jen jako vykonstruovaná obsese. I v tomte případě se ale místo jednoho dotazu budete potýkat s desítkami dotazů, které vyzvedávají - většinou zcela zbytečně - data jen pro jednu instanci OrderItem. Jestliže mám k tomu příležitost, je lepší vyzvednou všechna data pro všechny objekty najednou jedním "úsporným" dotazem a přitom dovolit v případě potřeby objektu přímé obnovení dat z databáze svým separátním dotazem.

Když tedy není pro nás přijatelný konstruktor s objektem DataRow, musíme při vytváření objektu "OrderItem" sdělit, odkud může načíst svá data. Jinými slovy, objekt OrderItem musí vědět, kam mu třída Order "schovala" objekt DataRow. Proč bychom ale neefektivně každému objektu extra sdělovali, odkud může načíst svá data? Zavedeme raději jeden centrální objekt - cache na příchozí data z databáze. Pak stačí objektu OrderItem předávat jen Id jako v první variantě a přitom  využívat všech výhod cachování dat.

Co od takové datové cache požadovat?

  1. Třída musí být globálně viditelná pro všechny objekty. Půjde tedy o Singleton, respektive pro webové aplikace bude vhodné použít PseudoSingleton (Thread Specific Storage).
  2. Rozhraní, které se nemění s přidáváním dalších business tříd a které mohou používat jednotným způsobem všechny objekty. Takže chceme univerzální metody pro ukládání i vyzvedávání dat jakéhokoli objektu, ne nějaké stále bobtnající rozhraní s metodami StoreOrdeItem, StoreOrder, StoreXX, StoreXY, ... .

Zde je jednoduchá implementace třídy, která zatím splňuje naše nároky. Zobrazit kód

Myslím, že kód je sám o sobě dostatečně vypovídající, takže jen stručný komentář. Statický atribut Instance vrátí pro každý thread specifickou instanci třídy, která se pro jeden thread chová jako běžný Singleton. Metoda CloseInstance odstraní instanci třídy pro daný thread a tuto metodu je vhodné volat ve webové aplikaci na konci každého požadavku v obsluze události EndRequest v souboru global.asax. Metoda StoreData uschová předaný datový řádek (argument row) pro objekt daného typu (argument type) - metoda předpokládá, že řádek obsahuje sloupec Id, který je jednoznačným identifikátorem každého business objektu. Metoda GetData vrátí dříve uložený řádek pro typ v argumentu type a pro objekt, jehož id bylo předáno v argumentu businessObjectId. Po vrácení dat je uložený řádek odstraněn, aby nebyly vlastnosti business objektu permanentně obnovovány z dříve uložených a již neaktuálních dat.

Přístě si ukážeme, jak si u business objektů vynutit používání třídy DataCacheHelper bez duplikace kódu a rizika, že zapomenete v nově přidané třídě řádky z instance DataCacheHelper vyzvedávat, a také postupně z DataCacheHelperu vydělíme různé aspekty jeho chování, abychom umožnili rekonfiguraci DataCacheHelperu za běhu aplikace a adekvátně vyladili jeho činnost pro různé typy aplikací.


Share/Save/Bookmark Sunday, August 07, 2005 7:26:34 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [11]  .NET Framework | Návrhové vzory


 Saturday, July 30, 2005
Řešení hádanky "Znáte dobře návrhové vzory"

Protože nikdo nedodal kompletní řešení k hádance z 26.7., zde je odpověď.

  1. Třída MessageReceiverBase - událost MessageReceived, návrhový vzor Observer.
  2. Třída MessageReceiverBase -metoda CreateMessage, návrhový vzor Factory Method. Metoda CreateMessage vrací přímo instancí třídy Message nebo její potomky. Potomci třídy MessageReceiverBase mohou metodu přepsat a vrátit z metody například instanci OrderMessage, aniž by byly dotčeny vysokoúrovňové scénaře pracující pouze s rozhraním Message a deklarované na úrovni MessageReceiverBase.
  3. Třídy MessageReceivePoint, MessageReceiverBase a její potomky můžeme považovat za participanty návrhového vzoru Bridge. Třída MessageReceivePoint je abstrakcí - logickým přístupovým bodem, který dokáže přijímat data na daném URI a který interně využívá konkrétní fyzický přístupový bod, jímž je potomek třídy MessageReceiverBase zapouzdřující detaily komunikace po zvoleném přenosovém protokolu. Metoda Listen třídy MessageReceivePoint deleguje volání na metodu Listen třídy MessageReceiverBase. Samozřejmě lze o potomcích třídy MessageReceiveBase uvaživat i jako o strategiích, jak zaznělo v diskuzi o spotu, i když si myslím, že vzor Bridge lépe vyjadřuje role tříd.
  4. Třída AddMesageAttributesProcessor - jak vyjadřuje její název, jedná se o realizátora vzoru Content Enricher - k přijaté zprávě dodává další informace, které nebyly přímo její součástí ale které jsou důležité pro další zpracování zprávy. Příklad - messagingový systém přijme objednávku a Content Enricher ke zprávě doplní údaje o platební morálce zákazníka.
  5. Třída FilterMessageAttributesProcessor - opět dle názvu můžete usuzovat, že jde o návrhový vzor Content Filter. Procesor z přijaté zprávy odstraní všechny informace, které nejsou důležité pro další zpracování a které by pouze zbytečně vytěžovaly zdroje serveru. Příklad - messagingový systém přijme obrázové přílohy, které není třeba posílat k dalšímu zpracování, ale pouze se archivují v DMS, takže nemá smysl hnát obrázky celým procesem vyřizování objednávky. Content Filter obrázky ze zprávy před jejím dalším zpracováním odstraní.

Jak někteří z vás (Petr :) ) správně vytušili, diagram také svádí k tomu, aby byl rozšířen o Intercepting filter nebo o vzor Pipes&Filters - tyto vzory v něm ale v současné podobě nalezneme poze jako latentní možnosti, které můžeme uskutečnit doplněním a úpravou vztahů mezi existujícími třídami.


Share/Save/Bookmark Saturday, July 30, 2005 5:22:25 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Návrhové vzory | Programátorské hádanky


 Wednesday, April 13, 2005
Ještě jedna prezentace z přednášky o návrhových vzorech

Jak mě upozornil jeden kolega, tak jsem nedal ke stažení prezentaci z přednášky o návrhových vzorech a .Net Frameworku z listopadového programátorského večera.

Takže pro zájemce napravuji:

V archivu ČVUT je i videozáznam přednášky, i když já sám jsem nenašel odvahu se na sebe narcisticko-kriticky podívat ;)

Prezentace, velikost 675 KB

 


Share/Save/Bookmark Wednesday, April 13, 2005 11:36:00 AM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Návrhové vzory | UML


 Sunday, April 03, 2005
Prezentace z .NET Developer Group o UML ke stažení
Hádanka

Na čtvrteční přednášce o UML jsem do svého rezervoáru životních paradoxů přidal pár dalších :)

1) I když sebekriticky zredukuju prezentaci o 150 slidech na 70, poté se intenzivní a zostřenou psychoanalytickou rychloseancí, na které by i těžkotonážní barokně rozsochatá lady Halina Pawlowská prodělala akutní záchvat mentální anorexie, jenž by byl excelentně konvertibilní na finační částku za nějakou další a zatraceně rychle natočenou reklamu na hubnutí s bonusem ve formě nezbytných JoJo baculatých efektů pro frustrované paničky, které jako ona chytře snídají dietní Colu s čokoládovými tyčinkami. :), vypořádám s pocity viny, jejichž zdrojem je obsedantní představa, že svatokrádežně zjednodušuju svá milovaná témata hodná nejvyšší úcty,  a přesto nenásleduje kýžená odměna, jíž měl suplovat fakt, že jsem ve vyhrazených 3 hodinách vykleštěné téma alespoň stihl probrat. ;)

2) Je zvláštní přijít tak rozpolcený z přednášky na výsostné půdě Microsoftu, protože jsem navzdory svému obdivu vůči mnoha MS produktům musel po dotazech posluchačů doporučovat modelovací nástroje od jiných firem a dát najevo svoji vytříbenou nechuť k "vytváření návrhů" ve Visiu a také si opět otravně povzdychnout nad tím, že Visual Studio Team System, MSF a UML nejsou a nebudou nerozlučným triem - killerem konkurenčních nástrojů pro návrh a vývoj aplikací. ;)

Zájemci si mohou stáhnout prezentaci, i když na návrhové vzory nezbyl ve čtvrtek čas a budou v rozšířené podobě tématem až nějaké další .Net Developer Group.


Share/Save/Bookmark Sunday, April 03, 2005 6:28:00 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [5]  Návrhové vzory | UML


 Sunday, September 26, 2004
Logování událostí v mediátoru

V mediátoru, kterého jsem popisoval v jednom dřívějším spotu, potřebujeme při testování logovat všechny příchozí události, které jsou předdefinovaným a bezpečným způsobem distribuovány dalším objektům. Nejméně náročnou cestou na napsání, volenou většinou vývojáři, kteří se pravděpodobně do IT rekrutují z rekvalifikovaných potomků nejzarytějších stachanovců;), je logování každé události zvláštní metodou. Po registaci nových událostí do mediátora je vždy i změněna komponenta pro logování, takže o údržbě takového kódu si můžete nechat zdát jen mrazivé sny o opětovném plíživém nahrazování kvality kvantitou.*

Na údržbu nenáročné a univerzální řešení spočívá ve vytvoření preprocesoru (viz spot o mediátoru), který je vyvolán před distribucí událostí ostatním objektům a který parametry události vypíše přes objekt Trace z jmenného prostoru System.Diagnostics.

Základní myšlenky v preprocesoru.

1) Většina (ne li každý) objekt v business vrstvě má unikátní identifikátor s názvem Id. Preprocesor události vypíše typ objektu, který událost vyvolal a jeho unikátní Id, takže je snadné dohledat zdroj problematických událostí.

2) Objekty nesoucí parametry událostí dědí vždy z třídy EventArgs. Přes reflection vypíšeme rekurzívně hodnoty vlastností do úrovně nastavené ve vlastnosti EndDumpLevel.

using System;
using System.Diagnostics;
using System.Reflection;
namespace RStein.Utilities
{
    /// <summary>
    ///  Třída EventsLog pro logování událostí
    /// </summary>
    public class EventsLog
    {
       
        #region private constants
        private const int BEGIN_DUMP_LEVEL = 1;
        private const int DEFAULT_END_DUMP_LEVEL = 2;
        #endregion private constants
        #region private variables
        private int m_endLevel;
        #endregion private variables
        #region Constructors
        /// <summary>
        /// Konstruktor
        /// </summary>
        public EventsLog() : base()
        {
            m_endLevel = DEFAULT_END_DUMP_LEVEL;
        }
        #endregion Constructors
        #region Public properties
        /// <summary>
        /// Koncová úroveň rekurzivního výpisu vlastností objeudálosti
        /// </summary>
        public int EndDumpLevel
        {
            get
            {
                return m_endLevel;
               
            }
            set
            {
                if (value < 1)
                    throw new ArgumentOutOfRangeException("Value must be greater than 1");
                
                m_endLevel = value;   
            }
           
        }
        #endregion Public properties
        #region Public methods
       
        /// <summary>
        /// Metoda pro výpis informace o událostech
        /// </summary>
        /// <param name="sender">Zdroj události</param>
        /// <param name="e">Parametry události</param>
        public void ProcessEvent(object sender, EventArgs e)
        {   
            Debug.Assert((sender != null) && (e != null), "EventsLog - Invalid event data");
           
            if ((sender == null)||
                (e == null))
            {
                    return;
            }
               
           
                Trace.WriteLine(new string('-',25));
                dumpId(sender);
                dumpEventProperties(e);
           
        }
        #endregion Public methods
        #region Private methods
       
        //Zapsání Id a názvu objektu
        private void dumpId (object sender)
        {
           
            Type senderType = sender.GetType();
            PropertyInfo idInfo = senderType.GetProperty("Id");
            string id = String.Empty;
           
            if (idInfo != null)
                id = idInfo.GetValue(sender, null).ToString();
            Trace.WriteLine(String.Format("Přijata událost od objektu {0}, Id = {1}", senderType.ToString(), id));
        }
        //Vypsani vlastností objektu s parametry události
        private void dumpEventProperties(EventArgs e)
        {
           
            Type eventType = e.GetType();
            Trace.WriteLine(String.Format("Objekt události -  {0}", eventType.ToString()));
            PropertyInfo[] eventInfo = eventType.GetProperties();
            Trace.WriteLine("Výpis hodnot událostí");
            writeEventValues(eventInfo, BEGIN_DUMP_LEVEL, e);
        }
        //Metoda pro rekurzivní výpis hodnot vlatností
        private void writeEventValues(PropertyInfo[] eventInfo, int level, object o)
        {
            foreach (PropertyInfo info in eventInfo)
            {
               
               
                //Nezpracovávat indexované vlastnosti
                if (info.GetIndexParameters().Length != 0)
                    continue;
               
                object propValue = info.GetValue(o, null);
               
               
                if (propValue == null)
                    continue;
               
                Trace.Write(new String('\t', level));
                Trace.WriteLine(String.Format("Jméno - {0}, Hodnota = {1}", info.Name, propValue.ToString()));
               
                if (level < EndDumpLevel)
                    writeEventValues(propValue.GetType().GetProperties(), level + 1, propValue);
            }
        }
       
        #endregion Private methods
    }
}
 
 
*Pozorný čtenář z tohoto spotu dokáže vyčíst i mou politickou orientaci a nechuť k určitému druhu myšlení;)


Share/Save/Bookmark Sunday, September 26, 2004 5:02:00 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  .NET Framework | Návrhové vzory


 Sunday, June 06, 2004
Synchronizace stavových automatů. Rozumíte návrhovému vzoru Mediator?

Návrhový vzor Mediator  zamezuje těsným vztahům mezi objekty zavedením  prostředníka (mediatora), který jejich interakci zapouzdří. Tak praví strohá, ale výstižná definice. Řečeno jinak, Mediator snižuje přímé i nepřímé zatížení třídy jinými třídami.

Vzor Mediator je většinou vykládán na příkladu interakce komponent v uživatelském rozhraní, které spolu komunikují přes prostředníka, jímž je formulář.  Listbox se seznamem zemí neví nic o Listboxu se sezamem měst - po výběru jiné země je vyvolána událostní procedura formuláře, v níž je kód pro naplnění druhého Listboxu městy ve vybrané zemi. Každý ovládací prvek spolupracuje jen s formulářem, který notifikuje o změně ve svých vlastnostech, a na formuláří je kód propagující změny do dalších prvků. Na způsobu chování nezáleží, formulář může být u prvku zaregistrován jako Observer (v terminologii jazyka JAVA Listener), jehož metoda je zavolána po změně stavu prvku, nebo v .NET zaregistrujeme u ovládacího prvku delegáta s obslužnou metodou, jejíž signatura se shoduje se signaturou události. Komplexního chování prezentační vrstvy není dosaženo přímou spoluprací mnoha objektů,  ale zavedením inteligentní mezivrstvy, která je recipientem heterogenních notifikačních zpráv objektů a současně  autorem a distributorem nových aktualizačních zpráv pro další objekty, jež po jejich přijetí změní svůj stav a přitom mohou prostředníkovi odeslat další notifikační zprávy. Zdůraznil bych, že prostředník je autorem zpráv, takže informaci o notifikaci nemusí jen přeposlat, ale také dokáže argumenty původní notifikační zprávy před odesláním kreativně pozměnit, filtrovat,  anebo dokonce rozešle události v jiném pořadí. Jak uvidíme, všechny tyto charakteristiky jsou klíčové pro mediatora koordinujícího synchronizaci stavových automatů business objektů.

Kdyby ovládací prvky mezi sebou komunikovaly přímo, tak i jednoduchá  změna chování prezentační vrstvy roztočí spirálu sisyfovského dohledávání a ošetřování dopadů změny v kódu všech prvků.

Použití vzoru Mediator ale není omezeno na prezentační vrstvu a jeho  kvality se plně projeví až při řízení složité interakce stavových automatů tříd v business vrstvě.

Představte si systém pro nábor zaměstnanců na různé pozice. V systému jsou evidovány nabízené pracovní pozice a uchazeči. Když uchazeč projeví zájem o pozici, je vytvořen nový objekt pro relaci mezi uchazečem a pozicí, v němž jsou uložena data o průběhu procesu výběru jednoho uchazeče. Platí pravidlo, že v jednom okamžiku může být Uchazeč přiřazen jen k jedné Pozici. Pozice,  Uchazeč  i Relace  mají své stavové diagramy. Uveďme si příklady stavů.

Pozice může být ve stavu:

 Nová - Zahájení životního cyklu pracovní pozice.

Přiřazen alespoň jeden uchazeč  O pozici se zajímá alespoň jeden uchazeč. Tento stav je důležitý pro manažery, kteří jsou odpovědni za obsazení pozice a zajímá je, zda například týden po vypsání místa je o pozici zájem, nebo je třeba investovat více peněz do propagace.

Přijat alespoň jeden uchazeč – K pozici se může vázat více volných míst, tento stav manažery náboru informuje, že již byl vybrán alespoň jeden vhodný zaměstnanec.

Obsazená – Pozice byla obsazena. Počet nabízených míst je roven počtu přijatých uchazečů.

Zrušená – Pozice byla zrušena. Pozici je možné kdykoli zrušit, což značí, že k ní již není možné dále přiřadit uchazeče a přiřazení uchazeči, kteří ještě nebyli přijati, jsou ihned odmítnuti.

Stavy uchazeče

Nový – Založení uchazeče.

 

Zajímá se o pozici Uchazeč byl přiřazen k pozici a prochází různými zkouškami. Nelze mu přiřadit další pozici.

 Přijat – Uchazeč byl přijat na pozici. Pozice musí být notifikována o přijetí uchazeče.

 Odmítnut – Uchazeč byl odmítnut, to znamená, že je ho možné přiřadit k jiné pozici.

 Propuštěn – Uchazeč (zaměstnanec) byl propuštěn. Opět ho je možné přiřadit k jiné pozici.

Zrušen – Uchazeč byl zrušen. Zrušit lze jen uchazeče ve stavu Nový, Odmítnutý a Propuštěný. Je nutné notifikovat aktivní relaci mezi Uchazečem a Pozicí.

Stavy relace mezi uchazečem a pozicí

Nová Nová relace mezi uchazečem a pozicí založena. Je nutné notifikovat pozici i uchazeče, že byla mezi nimi vytvořena relace.

Uchazeč prošel psychotesty – Uchazeč úspěšně prošel psychotesty.

Uchazeč odmítnut po psychotestech –Uchazeč neprošel psychotesty, notifikovat pozici a uchazeče o odmítnutí.

Uchazeč splnil všechny požadavky – Notifikovat pozici o přijetí dalšího uchazeče, notifikovat uchazeče.

Zrušena – Došlo ke zrušení relace, důvodem může být to, že uchazeč o pozici ztrati zájem nebo je zrušena pozice. Notifikovat uchazeče a pozici o zrušení.

Jde sice jen o reprezentativní množinu tříd a jejich stavů, ale i mezi takto malým počtem tříd existuje poměrně složitá spolupráce.

Například při zrušení pozice musejí být notifikovány všechny aktivní Relace, ty přejdou do stavu zrušena . Je notifikován Uchazeč, že byl odmítnut. Relace zpětně notifikuje o svém zrušení i Pozici, protože důvodem zrušení může být jindy ztráta zájmu ze strany Uchazeče.

Další příklad. Když relace přejde do stavu Uchazeč splnil všechny požadavky, tak musí být notifikována pozice, u níž dojde ke změně stavu jen tehdy, když se jedná o prvního přiřazeného uchazeče. Dále je notifikován uchazeč, který přejde do stavu Přijat.

Představme si nyní, že třídy Uchazeč, Pracovní pozice a Relace informaci o změně svého stavu nabídnou okolí ve formě událostí. Každý objekt z jedné třídy si přihlásí odběr událostí asociovaných objektů z dalších tříd  a pak všichni spustí divokou událostní přestřelku se zběsilými kulkami v podobě vzájemných notifikací.

Když Pozice přejde do stavu zrušena, informuje první relaci, ta změní svůj stav na zrušena, relace svoji změnu stavu zašle uchazeči, ten přejde do stavu „Odmítnut“ a vyvolá svoji událost „Změna stavu“, tu odchytí Relace, která na stav „Odmítnut“ u uchazeče nereaguje. Relace informaci o svém zrušení zasílá vždy recipročně objektu Pozice. Pozice pod dokončení kolečka s první Relací notifikuje další Relaci a kolečko s mírně obměněnými aktéry proběhne znovu. Všechny objekty jsou při tomto vzájemném přeřvávání kandidáty na slušivou svěrací kazajku.

V čem je ale hlavní problém? Události nemusí být zpracovány ve správném pořadí. Představme si, že u Pozice z nějakého důvodu zavedeme další tranzitivní stavy „Vyžádáno zrušení pozice“, „Pozice je rušena“ a teprve finálním stavem je náš původní stav „Zrušena“. Do stavu „Vyžádáno zrušení pozice“ přejde pozice ihned po žádosti uživatelem (přesněji po volání metody Cancel), do stavu „Pozice je rušena“ , když je zpětně notifikována první Relací o jejím přechodu do stavu Zrušena. Pozice přejde do finálního stavu  „Zrušena“ až po obdržení potvrzení o svém zrušení od všech dalších relací. Tady ale narazíme – když Pozice přejde do stavu „Pozice je rušena“, tak začne tuto událost rozesílat všem Relacím.

To znamená, že Relace2 a následující obdrží nejdříve informaci o přechodu Pozice do stavu „„Pozice je rušena“ a později o ( již nepravdivém)  přechodu do stavu  „Vyžádáno zrušení pozice“. Když se zkomplikují i stavové automaty dalších objektů, staneme se nechtěnými svědky rozhovoru bandy otrlých lhářů.:)

Pořadí při rozesílání událostí není sekvenční,  a i když bychom mohli do objektu Pozice a dalších dopsat logiku, která se o sekvenční distribuci postará, nepůjde o šťastné řešení. Aby třída konvenovala svému okolí, dosadíme do ní znalost, jak si okolí komunikací představuje, a porušíme tak princip zapouzdření. Třída svému okolí sděluje, jaké změny v ní nastaly, ale nesmí se starat o reakce okolí. Třída musí být vždy nonkonformní a kašlat  na své sousedy, jež ji nutí, aby se adaptovala na jejich způsob komunikace a chování.  (BTW: Zjsitil jsem, že  miluji OOP a návrhové vzory, protože k sousedům mám stejný postoj :) )

Odpovědnost za sekvenční rozesílání událostí přidělíme nové třídě Mediator. Po svém vytvoření budou objekty zaregistrovány v Mediatoru, který si přihlásí odběr jejich událostí. Třídám Pozice, Relace a Uchazeč přidáme metody, které budou volány mediátorem, když nastane událost. Například objekt Pozice bude mít metodu „NotifikujUchazečOdmítnut“, která bude mediátorem zavolána, když Relace přejde do stavu „Uchazeč odmítnut po psychotestech“ nebo do stavu  „Zrušena“.

Aby mediátor nebyl příliš komplikovaný a nepřehledný, vytvoříme pro každou třídu jednoho zpracovatele události. Každý zpracovatel událost bude implementovat rozhraní s jedinou metodou „ZpracujUdálost“, které přijímá dva argumenty. Prvním argumentem je odkaz na objekt, který událost vyvolal, a druhým jsou parametry události. Zpracovatel „ví“, jaké metody musí u objektů volat pro každou jedinečnou kombinaci argumentů události. (Když zpracovatel objektu Relace obdrží událost „Přechod do stavu Zrušena“, tak zavolá u asociovaného objektu Pozice metodu NotifikujUchazečOdmítnut“ a u Uchazeče metodu „ZůstávášNezaměstnaným“:) )

Jak zajistíme sekvenční zpracování?

V událostních metodách mediátor odchytne událost a zavolá vždy jen obecnou metodu handleEvent, které kromě odesílatele a argumentů události přijímá odkaz na zpracovatele (m_JobHandler), jenž reaguje na danou událost. Objekt m_JobHandler zpracovává událost „StavZměněn“ třídy Pracovní pozice.

handleEvent(sender, e, m_JobHandler);

 

Metoda handleEvent

 

private void handleEvent(object sender, EventArgs e, BProcessEventHandlerBase processor)

                        {

                                   EventStateHolder holder = new EventStateHolder(e, sender, processor);

                                   m_eventsQueue.Enqueue(holder);

 

                                   if (!m_inEventBubbling)

                                               initEventBubbling();

                        }

Metoda handleEvent nejdříve uloží všechny informace o události do nové instance naší třídyEventStateHolder. Zde mimochodem vidíte kouzlo polymorfismu, protože naše třída EventStateHolder se nestará o detaily událostí, ale vyžaduje jen, aby argumenty události byly odvozeny z třídy EventArgs a zpracovatelé událostí z třídy (rozhraní) BProcessEventHandlerBase. Poté je událost zařazena do fronty (v .NET třída Queue) a jestliže se jedná o první událost, je zavolána metoda initEventBubbling, která se postará o předání události do zpracovatele. Tento kód je důležitý –  bublání událostí je zahájeno jen při první události, všechny další události libovolných business objektů, které jsou přímou nebo nepřímou reakcí na první událost, jsou jen zařazeny do fronty a k novému  spuštění distribuce událostí realizovaného metodou initEventBubbling nesmí dojít.

private void initEventBubbling()

{

                                   Debug.Assert(m_eventsQueue.Count > 0, "BMediator events queue is empty!");

                                   m_inEventBubbling = true;

                                   while (m_eventsQueue.Count > 0)

                                   {

EventStateHolder eh = (EventStateHolder) m_eventsQueue.Dequeue();

eh.Processor.ProcessEvent(eh.Sender, eh.EventArguments);

                                   }                                 

m_inEventBubbling = false;

                        }

V metodě initEventBubbling se nejdříve ujistíme, že fronta událostí není prázdná. Pak nastavíme privátní proměnnou m_inEventBubbling na true, aby metoda handleEvent již při zpracovávání návazných událostí metodu initEventBubbling nevolala. V jednoduchém cyklu while zpracujeme sekvenčně všechny události postupně přidávané do fronty po rozeslání každé události.

Mediatora můžete vylepšit. Ke každé události mohou být registrovány preprocesory, kteří například zalogují všechny události a postprocesory, odesílající manažerovi informaci o provedených změnách. Když budeme chtít rychle změnit chování aplikace bez zásahu do existujícího kódu, můžeme napsat nový preprocesor, který událost zpracuje zcela jinak, a protože z metody vrátí true (nepokračovat v distribuci událostí, již jsem vše udělal), tak se originální zpracovatel události nedostane ke slovu. Preprocesory načteme dynamicky při startu aplikace  s využitím reflection API.

private void initEventBubbling()

{

                                   Debug.Assert(m_eventsQueue.Count > 0, "BMediator events queue is empty!");

                                  

                                   m_inEventBubbling = true;

                                   while (m_eventsQueue.Count > 0)

                                   {

                                               EventStateHolder eh = (EventStateHolder) m_eventsQueue.Dequeue();

                                               bool continueEventBubbling = distributeEventToPrePostProcessors(eh.Sender, eh.EventArguments, m_preProcessors);

                                               if (continueEventBubbling)

                                               {

                                                           eh.Processor.ProcessEvent(eh.Sender, eh.EventArguments);                                                        distributeEventToPrePostProcessors(eh.Sender, eh.EventArguments, m_postProcessors);

                                               }

                                   }

                                   m_inEventBubbling = false;

                        }

 

                        private bool distributeEventToPrePostProcessors(object sender, EventArgs e, ProcessorCollection processors)

                        {

                                   bool result = true;

                                   foreach (IProcessor processor in processors)

                                   {

                                               if ((processor.ProcessEvent(sender, e)))

                                               {

                                                           result = false;

                                                           break;

                                               }

                                              

                                   }

                                   return result;

                        }

Také můžete přes mediátora řídit distribuci událostí transakčně s potvrzováním a  vracením jednotlivých kroků a ukládat změny ve všech participujících objektech na konci úspěšné transakce do databáze.

Vzor Mediator je nepominutelnou dominantou každého návrhu aplikace, jež vyžaduje komplexní komunikaci mezi stavovými automaty různých tříd.

 

 


Share/Save/Bookmark Sunday, June 06, 2004 7:53:00 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [4]  Analytické drobky | UML | Návrhové vzory