Programmering

Java Tip 68: Lær hvordan du implementerer kommandomønsteret i Java

Designmønstre fremskynder ikke kun designfasen af ​​et objektorienteret (OO) projekt, men øger også produktiviteten i udviklingsteamet og softwarens kvalitet. EN Kommandomønster er et objektadfærdsmønster, der giver os mulighed for at opnå fuldstændig afkobling mellem afsenderen og modtageren. (EN afsender er et objekt, der påberåber en operation, og en modtager er et objekt, der modtager anmodningen om at udføre en bestemt operation. Med afkobling, afsenderen har intet kendskab til Modtagerinterface.) Udtrykket anmodning her henviser til den kommando, der skal udføres. Kommandomønsteret giver os også mulighed for at variere, hvornår og hvordan en anmodning bliver opfyldt. Derfor giver et kommandomønster os fleksibilitet såvel som udvidelse.

I programmeringssprog som C, funktionsmarkører bruges til at eliminere kæmpe switch-udsagn. (Se "Java Tip 30: Polymorfisme og Java" for en mere detaljeret beskrivelse.) Da Java ikke har funktionsmarkører, kan vi bruge kommandomønsteret til at implementere tilbagekald. Du ser dette i aktion i det første kodeeksempel nedenfor, kaldet Testkommando.java.

Udviklere, der er vant til at bruge funktionsmarkører på et andet sprog, kan blive fristet til at bruge Metode objekter i Reflection API på samme måde. For eksempel viser Paul Tremblett i sin artikel "Java Reflection" dig, hvordan du bruger Reflection til at gennemføre transaktioner uden at bruge switch-sætninger. Jeg har modstået denne fristelse, da Sun fraråder brug af Reflection API, når andre værktøjer, der er mere naturlige for Java-programmeringssproget, er tilstrækkelige. (Se Ressourcer for links til Tremblett's artikel og til Sun's Reflection tutorial-side.) Dit program bliver lettere at fejle og vedligeholde, hvis du ikke bruger Metode genstande. I stedet skal du definere en grænseflade og implementere den i de klasser, der udfører den nødvendige handling.

Derfor foreslår jeg, at du bruger kommandomønsteret kombineret med Java's dynamiske indlæsnings- og bindingsmekanisme til at implementere funktionspegere. (For detaljer om Java's dynamiske indlæsnings- og bindingsmekanisme, se James Gosling og Henry McGilton's "The Java Language Environment - A White Paper", der er anført i Resources.)

Ved at følge ovenstående forslag udnytter vi polymorfismen leveret af anvendelsen af ​​et kommandomønster til at eliminere kæmpe switch-udsagn, hvilket resulterer i udvidelige systemer. Vi udnytter også Javas unikke dynamiske indlæsnings- og bindingsmekanismer til at opbygge et dynamisk og dynamisk udvideligt system. Dette illustreres i det andet kodeeksempeleksempel nedenfor, kaldet TestTransactionCommand.java.

Kommandomønsteret gør selve anmodningen til et objekt. Dette objekt kan gemmes og sendes rundt som andre objekter. Nøglen til dette mønster er en Kommando interface, der erklærer en grænseflade til udførelse af operationer. I sin enkleste form inkluderer denne grænseflade et abstrakt udføre operation. Hver beton Kommando klasse angiver et modtager-handlingspar ved at gemme Modtager som en instansvariabel. Det giver forskellige implementeringer af udføre () metode til at påkalde anmodningen. Det Modtager har den nødvendige viden til at udføre anmodningen.

Figur 1 nedenfor viser Kontakt - en sammenlægning af Kommando genstande. Det har flipUp () og flipDown () operationer i dens grænseflade. Kontakt kaldes påkalder fordi det påberåber sig udførelsesoperationen i kommandogrænsefladen.

Den konkrete kommando, LightOnCommand, implementerer udføre betjening af kommandogrænsefladen. Det har viden til at kalde det rette Modtager objektets funktion. Det fungerer i dette tilfælde som en adapter. Ved udtrykket adapter, Jeg mener, at betonen Kommando objektet er et simpelt stik, der forbinder Invoker og Modtager med forskellige grænseflader.

Klienten instantierer Invoker, det Modtagerog de konkrete kommandoobjekter.

Figur 2, sekvensdiagrammet, viser interaktionerne mellem objekterne. Det illustrerer hvordan Kommando afkobler Invoker fra Modtager (og den anmodning, den gennemfører). Klienten opretter en konkret kommando ved at parametrere sin konstruktør med det relevante Modtager. Derefter gemmer den Kommando i Invoker. Det Invoker kalder den konkrete kommando tilbage, som har viden til at udføre det ønskede Handling() operation.

Klienten (hovedprogram i listen) skaber en beton Kommando objekt og indstiller dens Modtager. Som en Invoker objekt, Kontakt opbevarer betonen Kommando objekt. Det Invoker udsender en anmodning ved at ringe udføre på den Kommando objekt. Betonen Kommando objekt påberåber operationer på dets Modtager at udføre anmodningen.

Hovedideen her er, at den konkrete kommando registrerer sig selv med Invoker og Invoker kalder det tilbage og udfører kommandoen på Modtager.

Eksempelkode for kommandomønster

Lad os se på et simpelt eksempel, der illustrerer tilbagekaldsmekanismen opnået via kommandomønsteret.

Eksemplet viser en Ventilator og en Lys. Vores mål er at udvikle en Kontakt der kan tænde eller slukke for enten objekt. Vi ser, at Ventilator og Lys har forskellige grænseflader, hvilket betyder Kontakt skal være uafhængig af Modtager interface, eller den har intet kendskab til koden> Modtagerens interface. For at løse dette problem skal vi parametrere hver af Kontakts med den relevante kommando. Det er klart, at Kontakt tilsluttet til Lys har en anden kommando end Kontakt tilsluttet til Ventilator. Det Kommando Klassen skal være abstrakt eller en grænseflade for at dette kan fungere.

Når konstruktøren til en Kontakt påberåbes, parametriseres den med det relevante sæt kommandoer. Kommandoerne gemmes som private variabler af Kontakt.

Når flipUp () og flipDown () operationer kaldes, de vil simpelthen foretage den passende kommando til udføre (). Det Kontakt har ingen idé om, hvad der sker som et resultat af udføre () bliver kaldt.

TestCommand.java klasse Fan {public void startRotate () {System.out.println ("Fan roterer"); } public void stopRotate () {System.out.println ("Ventilator roterer ikke"); }} klasse Light {public void turnOn () {System.out.println ("Light is on"); } public void turnOff () {System.out.println ("Lyset er slukket"); }} klasse Skift {privat Kommando UpCommand, DownCommand; public Switch (Command Up, Command Down) {UpCommand = Op; // concrete Command registrerer sig hos invoker DownCommand = Down; } void flipUp () {// invoker kalder konkret Kommando tilbage, som udfører Kommandoen på modtageren UpCommand. udføre () } ugyldigt flipDown () {DownCommand. udføre () }} klasse LightOnCommand implementerer Command {private Light myLight; offentlig LightOnCommand (lys L) {myLight = L; } public void execute () {myLight. tænde for( ); }} klasse LightOffCommand implementerer Command {private Light myLight; offentlig LightOffCommand (lys L) {myLight = L; } public void execute () {myLight. sluk( ); }} klasse FanOnCommand implementerer Command {private Fan myFan; offentlig FanOnCommand (Fan F) {myFan = F; } public void execute () {myFan. startRotate (); }} klasse FanOffCommand implementerer Command {private Fan myFan; offentlig FanOffCommand (Fan F) {myFan = F; } public void execute () {myFan. stopRotate (); }} offentlig klasse TestCommand {public static void main (String [] args) {Light testLight = new Light (); LightOnCommand testLOC = ny LightOnCommand (testLight); LightOffCommand testLFC = ny LightOffCommand (testLight); Skift testSwitch = ny switch (testLOC, testLFC); testSwitch.flipUp (); testSwitch.flipDown (); Fan testFan = ny ventilator (); FanOnCommand foc = ny FanOnCommand (testFan); FanOffCommand ffc = ny FanOffCommand (testFan); Switch ts = ny switch (foc, ffc); ts.flipUp (); ts.flipDown (); }} Command.java public interface Command {public abstract void execute (); } 

Bemærk i kodeeksemplet ovenfor, at kommandomønsteret afkobler objektet, der påberåber operationen fuldstændigt - (Kontakt ) - fra dem, der har viden til at udføre det - Lys og Ventilator. Dette giver os stor fleksibilitet: objektet, der udsteder en anmodning, skal kun vide, hvordan man udsteder det; det behøver ikke at vide, hvordan anmodningen gennemføres.

Kommandomønster til implementering af transaktioner

Et kommandomønster er også kendt som et handling eller transaktionsmønster. Lad os overveje en server, der accepterer og behandler transaktioner leveret af klienter via en TCP / IP-stikforbindelse. Disse transaktioner består af en kommando efterfulgt af nul eller flere argumenter.

Udviklere bruger muligvis en switch-sætning med en sag til hver kommando. Anvendelse af Kontakt udsagn under kodning er et tegn på dårligt design i designfasen af ​​et objektorienteret projekt. Kommandoer repræsenterer en objektorienteret måde at understøtte transaktioner på og kan bruges til at løse dette designproblem.

I programmets klientkode TestTransactionCommand.java, alle anmodninger er indkapslet i det generiske TransactionCommand objekt. Det TransactionCommand konstruktør oprettes af klienten, og den registreres hos CommandManager. Forespørgslerne i kø kan udføres på forskellige tidspunkter ved at ringe til runCommands (), hvilket giver os en masse fleksibilitet. Det giver os også muligheden for at samle kommandoer i en sammensat kommando. jeg har også CommandArgument, Kommandomodtagerog CommandManager klasser og underklasser af TransactionCommand - nemlig Tilføj kommando og SubtractCommand. Følgende er en beskrivelse af hver af disse klasser:

  • CommandArgument er en hjælperklasse, der gemmer argumenterne for kommandoen. Det kan omskrives for at forenkle opgaven med at sende et stort eller variabelt antal argumenter af enhver type.

  • Kommandomodtager implementerer alle kommandobehandlingsmetoder og implementeres som et Singleton-mønster.

  • CommandManager er påkalderen og er den Kontakt svarende til det foregående eksempel. Det gemmer det generiske TransactionCommand objekt i sin private myCommand variabel. Hvornår runCommands () påberåbes, kalder det udføre () af det relevante TransactionCommand objekt.

I Java er det muligt at slå definitionen af ​​en klasse op, der får en streng, der indeholder dens navn. I udføre () drift af TransactionCommand klasse beregner jeg klassens navn og linker det dynamisk til det kørende system - det vil sige, at klasser indlæses på farten efter behov. Jeg bruger navngivningskonventionen, kommandonavnet sammenkædet af strengen "Kommando" som navnet på transaktionskommandoens underklasse, så den kan indlæses dynamisk.

Bemærk, at Klasse genstand returneret af newInstance () skal støbes til den passende type. Dette betyder, at den nye klasse enten skal implementere en grænseflade eller underklasse en eksisterende klasse, som er kendt af programmet på kompileringstidspunktet. I dette tilfælde, da vi implementerer Kommando interface, dette er ikke et problem.