AX2012: Události (II)

V předchozí části jsem ukazoval, jak umístit volání nějaké akce před nebo za určitou metodou. Dnešní článek popisuje jiný přístup k událostem, mnohem bližší událostem třeba v C#.

Programování řízené událostmi je v podstatě o tom, že jedna část aplikace vyvolává událost (třeba „tlačítko stisknuto“ nebo „záznam změněn“) a jiná část nebo části programu tuto událost zpracovávají (tzn. spouštějí nějaký kód, pokud k dané události dojde). Třída vyvolávající událost nemá žádnou informaci o zpracovatelích svých událostí, což umožňuje zmenšit závislosti mezi jednotlivými komponentami.

Předpis, jaká metoda může být konkrétní události přiřazena (a tedy volána ve chvíli, kdy k události dojde), je definována pomocí delegátu. Delegát v Dynamics AX 2012 určuje počet a typ parametrů, umožňuje přiřazení ovladačů události (event handlers) a umožňuje vyvolat událost – ve srovnání se C# je to spíše takový mix konstruktů delegate a event, než samotný delegate.

Událost je vyvolána voláním delegátu, jako by to byla běžná metoda. Tím jsou aktivovány handlery, které byly této události přiřazeny.

Ukažme si to na jednoduchém příkladu. Mějme třídu simulující automatický schvalovací proces (Approval), vyvolávající dvě události: schváleno (approved) a odmítnuto (rejected). Pomocí New > Delegate v ní vytvoříme dva delegáty (všimněte si ikony blesku v AOT), nazveme je approved a rejected a necháme je akceptovat parametr typu Approval:

public class Approval
{
    delegate void approved(Approval _sender){}
    delegate void rejected(Approval _sender){}
}

Delegát v AX2012 je vždy veřejný a má návratový typ void (protože může vyvolávat více metod, měl by potenciálně více návratových hodnot). Není to ale metoda a nemůže obsahovat žádný kód.

Co se týče jmenných konvencí a parametrů, není AX2012 příliš konzistentní. Většina delegátů je definována s příponou „EventHandler“ (např. closedViewEventHandler), část používá předponu „on“ (OnFileCreated) a část používá jen logické jméno (renderingCompleted). Dokonce ani velikost prvního písmena není jednotná. Bylo by užitečné, kdyby Microsoft definoval nějakou konvenci a respektoval ji ve svém kódu.

Mimochodem, všimněte si, že páry událostí „před“ a „po“ (např. finalizingEventHandler a finalizedEventHandler) jsou i v AX často využívané.

Joris de Gruyter ve svém článku o událostech v AX2012  doporučuje používat jako první parametr objekt vyvolávající událost (_sender) a jako druhý parametr objekt dědící od XppEventArgs. Jakkoli je tento přístup konzistentní s .NET, v AX2012 jsem našel jediný delegát respektující ono druhé pravidlo (jmenovitě SrsReportRunController.renderingCompleted), takže to určitě není standardní přístup vývojářů Dynamics AX 2012. V tuto chvíli bych to tedy neprosazoval.

Zpátky k příkladu. Ve třídě Approval ještě doplníme simulaci byznys logiky vyvolávající příslušné události. Všimněte si, že volání delegátu je stejné jako volání metody.

public class Approval
{
    delegate void approved(Approval _sender){}
    delegate void rejected(Approval _sender){}
    public void approve()
    {
        if (new RandomGenerate().randomInt(0, 1))
        {
            this.approved(this); //volání delegátu
        }
        else
        {
            this.rejected(this); //volání delegátu
        }
    }
 }

Tím je schvalovací třída hotová a připravená k použití. Třídy, které ji budou využívat, se nemusí starat o nic víc než jak (či zda vůbec) zpracovávat vyvolané události.

Následující třída využívá třídu Approval a zpracovává obě její události. Event handlery jsou přiřazeny ve zdrojovém kódu, ačkoli statické handlery mohou být přiřazeny také v AOT.

public class Process
{
    public static void main(Args _args)
    {
        new Process().run();
    }
    public void run()
    {
        Approval approval = new Approval();
        //přiřazení event handlerů
        approval.approved += eventhandler(this.doWhenApproved);
        approval.rejected += eventhandler(this.doWhenRejected);
        approval.approve();
    }
    public void doWhenApproved(Approval _approval)
    {
        info("Approved");
    }
    public void doWhenApproved(Approval _approval)
    {
        info("Rejected");
    }
}

Třída je velmi jednoduchá, za pozornost stojí jen přiřazení handlerů k událostem. Stejně jako v C#, k přidání ovladače slouží operátor += a k odebrání operátor -=. Neexistuje tedy žádný operátor jednoduchého přiřazení, který by přepsal existující ovladače dané události. Na pravé straně přiřazení je klíčové slovo eventhandler a identifikace metody, která vystupuje jako event handler. Ta musí být public, mít návratový typ void a stejné parametry jako delegát na levé straně přiřazení.

Pokud má událost několik handlerů přiřazených v kódu, není pořadí jejich volání konkrétně určeno (experimentálně jsem ověřil, že opakovaná volání stejného kódu mohou volat handlery v různém pořadí).

Pokud by byly handlery (z předchozího příkladu) definovány jako statické metody, přiřazení k událostem by vypadalo takto:

approval.approved += eventhandler(Process::doWhenApproved);
approval.rejected += eventhandler(Process::doWhenRejected);

Tímto jsem ukázal syntaktickou stránku věci, ale to podstatné leží v nových možnostech, které události nabízejí při objektovém modelování. Jako řada jiných novinek v AX2012 i události vyžadují jistou změnu v myšlení, ale umožňují snáze řešit určité typy problémů a také přebírat některá řešení a návrhové vzory z jiných jazyků.