Java savjet 68: naučite kako implementirati naredbeni obrazac u Javi

Obrasci dizajna ne samo da ubrzavaju fazu dizajna objektno orijentiranog (OO) projekta, već i povećavaju produktivnost razvojnog tima i kvalitetu softvera. Uzorak naredbe je obrazac ponašanja objekta koji nam omogućuje postizanje potpune razdvojenosti između pošiljatelja i primatelja. ( Pošiljatelj je objekt koji poziva operaciju, a primatelj je objekt koji prima zahtjev za izvršenje određene operacije. Uz razdvajanje, pošiljatelj nema znanja o Receiversučelju '.) Izraz zahtjevovdje se odnosi na naredbu koja se treba izvršiti. Uzorak naredbe također nam omogućuje da razlikujemo kada i kako se ispunjava zahtjev. Stoga nam naredbeni obrazac pruža fleksibilnost kao i proširivost.

U programskim jezicima poput C, pokazivači na funkcije koriste se za uklanjanje divovskih naredbi prekidača. (Pogledajte "Java savjet 30: Polimorfizam i Java" za detaljniji opis.) Budući da Java nema pokazivače na funkcije, možemo koristiti Command pattern za implementaciju povratnih poziva. To ćete vidjeti na djelu u prvom primjeru koda u nastavku, nazvanom TestCommand.java.

Razvojni programeri naviknuti na upotrebu pokazivača funkcija na drugom jeziku mogli bi doći u iskušenje da Methodna isti način koriste objekte API-ja Reflection. Na primjer, u svom članku "Java Reflection", Paul Tremblett vam pokazuje kako koristiti Reflection za provedbu transakcija bez upotrebe naredbi switch. Odupirao sam se ovoj napasti, jer Sun savjetuje da se ne koristi Reflection API kad će biti dovoljni drugi alati prirodniji za programski jezik Java. (Pogledajte Resurse za poveznice do Tremblettovog članka i stranice udžbenika Sun's Reflection.) Vaš će program biti lakši za otklanjanje pogrešaka i održavanje ako ne koristite Methodobjekte. Umjesto toga, trebali biste definirati sučelje i implementirati ga u klase koje izvode potrebnu radnju.

Stoga vam predlažem da koristite Command pattern u kombinaciji s Java-inim dinamičkim mehanizmom učitavanja i vezanja za implementaciju pokazivača na funkcije. (Za detalje o Java-inom dinamičkom mehanizmu učitavanja i vezanja pogledajte James Gosling i Henry McGilton "Java Java Environment - A White Paper", navedeni u Resursima.)

Slijedeći gornju sugestiju, iskorištavamo polimorfizam koji pruža primjena naredbenog uzorka za uklanjanje divovskih naredbi prekidača, što rezultira proširivim sustavima. Također koristimo jedinstvene Java-ove mehanizme dinamičkog učitavanja i vezanja za izgradnju dinamičnog i dinamički proširivog sustava. To je ilustrirano u drugom primjeru donjeg primjera koda, pod nazivom TestTransactionCommand.java.

Uzorak naredbe pretvara sam zahtjev u objekt. Ovaj se objekt može pohraniti i prenositi poput ostalih predmeta. Ključ ovog uzorka je Commandsučelje koje deklarira sučelje za izvršavanje operacija. U svom najjednostavnijem obliku, ovo sučelje uključuje apstraktnu executeoperaciju. Svaka konkretna Commandklasa određuje par prijamnik-radnja spremanjem Receivervarijable instance. Pruža različite implementacije execute()metode za pozivanje zahtjeva. ReceiverIma znanje potrebno za obavljanje zahtjev.

Slika 1 dolje prikazuje Switch- agregaciju Commandobjekata. U svom sučelju ima flipUp()i flipDown()operacije. Switchnaziva se pozivateljem jer poziva naredbu izvršavanja u naredbenom sučelju.

Konkretna naredba, LightOnCommandprovodi executerad naredbenog sučelja. Ima znanje da pozove rad odgovarajućeg Receiverobjekta. U ovom slučaju djeluje kao adapter. Pod pojmom adapter mislim na to da je konkretni Commandobjekt jednostavan konektor koji povezuje Invokeri Receivers različitim sučeljima.

Klijent instancira naredbe Invoker, the Receiveri konkretne naredbene objekte.

Slika 2, dijagram sekvence, prikazuje interakcije između objekata. Ilustrira kako se Commandrazdvaja Invokerod Receiver(i zahtjeva koji se izvršava). Klijent stvara konkretnu naredbu parametariziranjem svog konstruktora odgovarajućim Receiver. Zatim sprema datoteku Commandu Invoker. U Invokernazove naredbu beton, koji ima znanje za izvođenje željene Action()operacije.

Klijent (glavni program na popisu) kreira konkretni Commandobjekt i postavlja ga Receiver. Kao Invokerobjekt Switchčuva konkretni Commandobjekt. InvokerIzdaje zahtjev pozivajući executese na Commandobjekt. Konkretni Commandobjekt poziva se na operacije Receiverizvršenja zahtjeva.

Ključna ideja ovdje je da se konkretna naredba registrira s Invokeri Invokerpozove ga, izvršavajući naredbu na Receiver.

Primjer uzorka naredbenog koda

Pogledajmo jednostavan primjer koji ilustrira mehanizam povratnog poziva postignut kroz Command obrazac.

Primjer prikazuje a Fani a Light. Naš je cilj razviti uređaj Switchkoji može uključiti ili isključiti bilo koji objekt. Vidimo da a Fani Lightimaju različita sučelja, što znači da Switchmora biti neovisno o Receiversučelju ili da ne poznaje kod> Sučelje prijemnika. Da bismo riješili taj problem, moramo parameterizirati svaku od Switchs odgovarajućom naredbom. Očito je Switchda Lightće povezani s voljom imati drugačiju naredbu od onih Switchpovezani s Fan. Predavanje Commandmora biti apstraktno ili sučelje da bi to uspjelo.

Kada Switchse pozove konstruktor za a , on se parametrizira odgovarajućim skupom naredbi. Naredbe će biti pohranjene kao privatne varijable Switch.

Kada su flipUp()te flipDown()operacije nazivaju, oni će jednostavno napraviti odgovarajuću naredbu execute( ). Will Switchneće imati pojma što se događa kao rezultat execute( )poziva.

Klasa TestCommand.java Fan {public void startRotate () {System.out.println ("Ventilator se okreće"); } public void stopRotate () {System.out.println ("Ventilator se ne okreće"); }} class Light {public void turnOn () {System.out.println ("Svjetlo je uključeno"); } public void turnOff () {System.out.println ("Svjetlo je isključeno"); }} klasa Switch {private Command UpCommand, DownCommand; javni prekidač (naredba gore, naredba dolje) {UpCommand = gore; // konkretna naredba registrira se s pozivateljem DownCommand = Down; } void flipUp () {// invoker poziva konkretnu naredbu, koja izvršava naredbu na prijemniku UpCommand. izvršiti (); } void flipDown () {DownCommand. izvršiti (); }} klasa LightOnCommand provodi naredbu {private Light myLight; javni LightOnCommand (Svjetlo L) {myLight = L;} javna praznina execute () {myLight. upaliti( ); }} klasa LightOffCommand implementira naredbu {private Light myLight; javni LightOffCommand (Svjetlo L) {myLight = L; } javna praznina execute () {myLight. turnOff (); }} klasa FanOnCommand implementira naredbu {private Fan myFan; javni FanOnCommand (Fan F) {myFan = F; } javna praznina execute () {myFan. startRotate (); }} klasa FanOffCommand implementira naredbu {private Fan myFan; javni FanOffCommand (Fan F) {myFan = F; } javna praznina execute () {myFan. stopRotate (); }} javna klasa TestCommand {public static void main (String [] args) {Light testLight = new Light (); LightOnCommand testLOC = novi LightOnCommand (testLight); LightOffCommand testLFC = novi LightOffCommand (testLight); Switch testSwitch = novi Switch (testLOC, testLFC); testSwitch.flipUp (); testSwitch.flipDown ();Test ventilatoraFan = novi ventilator (); FanOnCommand foc = novi FanOnCommand (testFan); FanOffCommand ffc = novi FanOffCommand (testFan); Switch ts = novi Switch (foc, ffc); ts.flipUp (); ts.flipDown (); }} Command.java javno sučelje Naredba {public abstract void execute (); }

Primijetite u gornjem primjeru koda da obrazac Command u potpunosti razdvaja objekt koji poziva operaciju - (Switch )- od onih koji imaju znanje da je izvedu - Lighti Fan. To nam daje veliku fleksibilnost: objekt koji izdaje zahtjev mora znati samo kako ga izdati; ne treba znati kako će se zahtjev izvršiti.

Uzorak naredbe za provedbu transakcija

Uzorak naredbe poznat je i kao obrazac radnje ili transakcije. Razmotrimo poslužitelj koji prihvaća i obrađuje transakcije koje klijenti isporučuju putem TCP / IP priključka na utičnicu. Te se transakcije sastoje od naredbe, nakon koje slijedi nula ili više argumenata.

Razvojni programeri mogu upotrijebiti naredbu switch s velikim i malim slovima za svaku naredbu. Korištenje Switchizjava tijekom kodiranja znak je lošeg dizajna tijekom faze projektiranja objektno orijentiranog projekta. Naredbe predstavljaju objektno orijentirani način podrške transakcijama i mogu se koristiti za rješavanje ovog problema dizajna.

U klijentskom kodu programa TestTransactionCommand.java, svi su zahtjevi inkapsulirani u generički TransactionCommandobjekt. TransactionCommandKonstruktor je stvoren od strane klijenta i registriran s CommandManager. Zahtjevi u redu mogu se izvršiti u različito vrijeme pozivanjem runCommands(), što nam daje veliku fleksibilnost. Također nam daje mogućnost okupljanja naredbi u složenu naredbu. Imam CommandArgument, CommandReceiveri CommandManagerklase i potklase TransactionCommand- naime AddCommandi SubtractCommand. Slijedi opis svake od ovih klasa:

  • CommandArgumentje pomoćna klasa koja pohranjuje argumente naredbe. Može se prepisati radi pojednostavljenja zadatka prosljeđivanja velikog ili promjenjivog broja argumenata bilo koje vrste.

  • CommandReceiver implementira sve metode obrade naredbi i provodi se kao Singleton obrazac.

  • CommandManagerje zazivač i Switchekvivalent je iz prethodnog primjera. Pohranjuje generički TransactionCommandobjekt u njegovu privatnu myCommandvarijablu. Kada runCommands( )se pozove, poziva execute( )odgovarajućeg TransactionCommandobjekta.

U Javi je moguće potražiti definiciju klase s obzirom na niz koji sadrži njezino ime. U execute ( )operaciji TransactionCommandklase izračunavam naziv klase i dinamički ga povezujem s pokrenutim sustavom - to jest, klase se učitavaju u letu prema potrebi. Upotrebljavam konvenciju imenovanja, ime naredbe spojeno nizom "Naredba" kao ime podklase naredbe transakcije, tako da se može dinamički učitati.

Primijetite da Classobjekt koji je vratio newInstance( )mora biti prebačen na odgovarajući tip. To znači da nova klasa mora ili implementirati sučelje ili podklasu postojeće klase koja je programu poznata u vrijeme kompajliranja. U ovom slučaju, budući da implementiramo Commandsučelje, to nije problem.