Programování

Tip pro Javu 68: Naučte se, jak implementovat příkazový vzor v Javě

Konstrukční vzory nejen urychlují fázi návrhu objektově orientovaného projektu (OO), ale také zvyšují produktivitu vývojového týmu a kvalitu softwaru. A Vzor příkazu je vzor chování objektu, který nám umožňuje dosáhnout úplného oddělení mezi odesílatelem a příjemcem. (A odesílatel je objekt, který vyvolá operaci, a přijímač je objekt, který obdrží požadavek na provedení určité operace. S oddělení, odesílatel nemá žádné znalosti o PřijímačRozhraní.) Termín žádost zde odkazuje na příkaz, který má být proveden. Příkazový vzor nám také umožňuje měnit, kdy a jak je požadavek splněn. Proto nám příkazový vzor poskytuje flexibilitu i rozšiřitelnost.

V programovacích jazycích jako C, ukazatele funkcí se používají k eliminaci obřích přepínacích příkazů. (Podrobnější popis najdete v části „Java Tip 30: Polymorphism and Java“.) Protože Java nemá funkční ukazatele, můžeme k implementaci zpětných volání použít příkazový vzor. Uvidíte to v akci v prvním příkladu kódu níže s názvem TestCommand.java.

Vývojáři zvyklí používat ukazatele funkcí v jiném jazyce mohou být v pokušení použít Metoda objekty Reflection API stejným způsobem. Například ve svém článku „Java Reflection“ vám Paul Tremblett ukazuje, jak pomocí Reflection implementovat transakce bez použití příkazů switch. Odolal jsem tomuto pokušení, protože Sun nedoporučuje používat Reflection API, když budou stačit jiné nástroje přirozenější pro programovací jazyk Java. (Odkazy na článek o Tremblettovi a na stránku s výukovým programem Sun Reflection najdete v Zdrojích.) Pokud program nepoužíváte, bude snazší jej ladit a udržovat. Metoda předměty. Místo toho byste měli definovat rozhraní a implementovat ho do tříd, které provádějí potřebnou akci.

Proto navrhuji, abyste k implementaci ukazatelů funkcí použili příkazový vzor v kombinaci s mechanismem dynamického načítání a vazby Java. (Podrobnosti o mechanismu dynamického načítání a vazby Java najdete v dokumentu James Gosling a Henry McGilton „The Java Language Environment - A White Paper“, uvedený ve zdrojích.)

Podle výše uvedeného návrhu využijeme polymorfismus poskytovaný aplikací vzoru příkazů k eliminaci obřích příkazů přepínače, což vede k rozšiřitelným systémům. Také využíváme jedinečné dynamické mechanismy načítání a vazby Java k vytvoření dynamického a dynamicky rozšiřitelného systému. To je znázorněno na druhém příkladu kódu níže, který se nazývá TestTransactionCommand.java.

Vzor příkazu změní samotný požadavek na objekt. Tento objekt lze ukládat a předávat jako ostatní objekty. Klíčem k tomuto vzoru je a Příkaz interface, který deklaruje rozhraní pro provádění operací. Ve své nejjednodušší podobě obsahuje toto rozhraní abstrakt vykonat úkon. Každý beton Příkaz třída určuje dvojici přijímač-akce uložením Přijímač jako proměnná instance. Poskytuje různé implementace vykonat() metoda k vyvolání požadavku. The Přijímač má znalosti potřebné k provedení žádosti.

Obrázek 1 níže ukazuje Přepínač - agregace Příkaz předměty. Má to otočit nahoru() a flipDown () operace v jeho rozhraní. Přepínač se nazývá vyvolávač protože vyvolá operaci spuštění v příkazovém rozhraní.

Konkrétní příkaz, LightOnCommand, implementuje vykonat činnost příkazového rozhraní. Má znalosti nazývat vhodné Přijímač provoz objektu. V tomto případě funguje jako adaptér. Termínem adaptér, Myslím tím beton Příkaz objekt je jednoduchý konektor, spojující Vyvolávač a Přijímač s různými rozhraními.

Klient vytvoří instanci VyvolávačPřijímača konkrétní příkazové objekty.

Obrázek 2, sekvenční diagram, ukazuje interakce mezi objekty. Ilustruje to jak Příkaz odděluje Vyvolávač z Přijímač (a žádost, kterou provádí). Klient vytvoří konkrétní příkaz parametrizací svého konstruktoru příslušnými Přijímač. Poté uloží Příkaz v Vyvolávač. The Vyvolávač zavolá zpět konkrétní příkaz, který má znalosti k provedení požadovaného Akce() úkon.

Klient (hlavní program v seznamu) vytvoří konkrétní Příkaz objekt a nastaví jeho Přijímač. Jako Vyvolávač objekt, Přepínač ukládá beton Příkaz objekt. The Vyvolávač vydá požadavek voláním vykonat na Příkaz objekt. Beton Příkaz objekt vyvolává operace na svém Přijímač k provedení žádosti.

Klíčovou myšlenkou je, že konkrétní příkaz se zaregistruje u Vyvolávač a Vyvolávač zavolá zpět a provede příkaz na Přijímač.

Příklad kódu příkazu

Pojďme se podívat na jednoduchý příklad ilustrující mechanismus zpětného volání dosažený pomocí příkazového vzoru.

Příklad ukazuje a Fanoušek a Světlo. Naším cílem je vyvinout a Přepínač který může zapnout nebo vypnout libovolný objekt. Vidíme, že Fanoušek a Světlo mají různá rozhraní, což znamená Přepínač musí být nezávislý na Přijímač rozhraní nebo nemá žádné znalosti kódu> rozhraní přijímače. Abychom tento problém vyřešili, musíme každý z nich parametrizovat Přepínačs příslušným příkazem. Je zřejmé, že Přepínač připojeno k Světlo bude mít jiný příkaz než Přepínač připojeno k Fanoušek. The Příkaz třída musí být abstraktní nebo rozhraní, aby to fungovalo.

Když konstruktor pro a Přepínač je vyvolána, je parametrizována příslušnou sadou příkazů. Příkazy budou uloženy jako soukromé proměnné souboru Přepínač.

Když otočit nahoru() a flipDown () operace se nazývají, jednoduše provedou příslušný příkaz provést (). The Přepínač nebude mít tušení, co se stane v důsledku provést () být volán.

TestCommand.java třída Fan {public void startRotate () {System.out.println ("Fan se otáčí"); } public void stopRotate () {System.out.println ("Ventilátor se neotáčí"); }} třída Light {public void turnOn () {System.out.println ("Light is on"); } public void turnOff () {System.out.println ("Světlo nesvítí"); }} Přepínač třídy {private Command UpCommand, DownCommand; veřejný přepínač (příkaz nahoru, příkaz dolů) {UpCommand = nahoru; // konkrétní příkaz se zaregistruje u vyvolávače DownCommand = Down; } void flipUp () {// vyvolávač zavolá zpět konkrétní příkaz, který provede příkaz na přijímači UpCommand. vykonat (); } void flipDown () {DownCommand. vykonat (); }} třída LightOnCommand implementuje příkaz {private Light myLight; public LightOnCommand (Light L) {myLight = L; } public void execute () {myLight. zapnout( ); }} třída LightOffCommand implementuje příkaz {private Light myLight; public LightOffCommand (Light L) {myLight = L; } public void execute () {myLight. vypnout( ); }} třída FanOnCommand implementuje příkaz {private Fan myFan; public FanOnCommand (Fan F) {myFan = F; } public void execute () {myFan. startRotate (); }} třída FanOffCommand implementuje příkaz {private Fan myFan; public FanOffCommand (Fan F) {myFan = F; } public void execute () {myFan. stopRotate (); }} public class TestCommand {public static void main (String [] args) {Light testLight = new Light (); LightOnCommand testLOC = nový LightOnCommand (testLight); LightOffCommand testLFC = nový LightOffCommand (testLight); Přepínač testSwitch = nový přepínač (testLOC, testLFC); testSwitch.flipUp (); testSwitch.flipDown (); Test ventilátoru = nový ventilátor (); FanOnCommand foc = nový FanOnCommand (testFan); FanOffCommand ffc = nový FanOffCommand (testFan); Přepínač ts = nový přepínač (foc, ffc); ts.flipUp (); ts.flipDown (); }} Veřejné rozhraní Command.java Příkaz {public abstract void execute (); } 

Ve výše uvedeném příkladu kódu si všimněte, že příkazový vzor zcela odděluje objekt, který vyvolá operaci - (Přepnout) - od těch, kteří mají znalosti k jejich provedení - Světlo a Fanoušek. To nám dává velkou flexibilitu: objekt vydávající požadavek musí vědět jen to, jak jej vydat; nepotřebuje vědět, jak bude žádost provedena.

Vzor příkazu k implementaci transakcí

Vzor příkazu je také známý jako akce nebo vzor transakce. Uvažujme o serveru, který přijímá a zpracovává transakce doručované klienty prostřednictvím soketového připojení TCP / IP. Tyto transakce se skládají z příkazu, za kterým následuje nula nebo více argumentů.

Vývojáři mohou použít příkaz switch s případem pro každý příkaz. Využití Přepínač příkazy během kódování jsou známkou špatného designu během fáze návrhu objektově orientovaného projektu. Příkazy představují objektově orientovaný způsob podpory transakcí a lze je použít k řešení tohoto konstrukčního problému.

V klientském kódu programu TestTransactionCommand.java, všechny požadavky jsou zapouzdřeny do obecného TransactionCommand objekt. The TransactionCommand konstruktor je vytvořen klientem a je registrován u CommandManager. Požadavky ve frontě lze provést v různých časech voláním runCommands (), což nám dává velkou flexibilitu. Také nám dává možnost sestavit příkazy do složeného příkazu. také mám CommandArgument, CommandReceiver, a CommandManager třídy a podtřídy TransactionCommand - jmenovitě AddCommand a SubtractCommand. Následuje popis každé z těchto tříd:

  • CommandArgument je pomocná třída, která ukládá argumenty příkazu. Lze jej přepsat, aby se zjednodušil úkol předávání velkého nebo variabilního počtu argumentů libovolného typu.

  • CommandReceiver implementuje všechny metody zpracování příkazů a je implementován jako vzor Singleton.

  • CommandManager je vyvolávač a je Přepínač ekvivalent z předchozího příkladu. Ukládá generikum TransactionCommand objekt v soukromí myCommand proměnná. Když runCommands () je vyvolána, volá provést () příslušného TransactionCommand objekt.

V Javě je možné vyhledat definici třídy s řetězcem obsahujícím její název. V provést () provoz TransactionCommand třídy, spočítám název třídy a dynamicky ji propojím s běžícím systémem - to znamená, že třídy se načítají za běhu podle potřeby. Jako název podtřídy příkazu transakce používám konvenci pojmenování, název příkazu zřetězený řetězcem „Command“, aby jej bylo možné dynamicky načíst.

Všimněte si, že Třída objekt vrácený newInstance () musí být seslán na vhodný typ. To znamená, že nová třída musí buď implementovat rozhraní, nebo podtřídu existující třídy, která je programu známa v době kompilace. V tomto případě, protože implementujeme Příkaz rozhraní, to není problém.

$config[zx-auto] not found$config[zx-overlay] not found