© 1999-2003, Flemming Koch Jensen
Alle rettigheder forbeholdt |
Command Pattern Opgaver |
Forudsætninger: Det forudsættes, at man har kendskab til menuer i Swing (i forbindelse med eksemplet). Da menuer er det primære anvendelsesområde for Command Pattern vil det være uhensigtsmæssigt at beskæftige sig med dette pattern uden at se det anvendt i forbindelse med netop menuer. |
Motivation | |||
Det kan være bekvemt at kunne ændre, hvad der sker når man aktiverer visse ting i et program, og at disse ændringer kan ske mens programmet udføres. F.eks. kan det være rart at kunne ændre i en applikations menuer - hvad der sker når man vælger et menupunkt - mens applikationen kører, og at denne ændring får effekt med det samme. | |||
Ofte er den slags systemer opbygges med følgende design for øje: | |||
Figur 1: At hændelse opstår i ét objekt, fører til udførelse af funktionalitet i et andet objekt |
| ||
Designet har et grundlæggende problem. Koblingen mellem de to objekter vil ofte være stærk, og det giver derfor problemer at udskifte det udførende objekt med et andet. | |||
Ingen af dem kan/vil tilpasse sig den anden | En nærliggende måde at løse problemet på, ville være ved at lade de forskellige "funktions-objekter" realisere det samme interface, men vi ønsker at kunne anvende vidt forskellige objekter som "funktions-objekter". Situationen er derfor den, at objektet hvor hændelsen opstår ikke kan have noget at gøre med hvad der konkret skal ske, da vi ellers ikke ville kunne ændre funktionaliteten dynamisk. Samtidig vil "funktions-objektet" ikke tilpasse sig, at det bliver brugt i ovenstående sammenhæng. Alt i alt to objekter der ikke er smidige i vores design. | ||
Problem | |||
Vi ønsker nemt at kunne udskifte en funktionalitet, der udspringer af en hændelse i ét objekt, men udføres i et andet objekt. Dette besværliggøres af en stærk kobling mellem objekterne. | |||
Løsning | |||
Løsningen er at placere et objekt mellem de to objekter: | |||
Figur 2: Ønsket om udførelse af funktionalitet delegeres via et mellemliggende objekt |
| ||
Når der opstår en hændelse, der skal føre til udførelsen af en funktionalitet, vil requesten blive delegeret via et mellemliggende objekt. På den måde kan vi opnå et abstrakt kendskab mellem det objekt hvor hændelsen opstår og det hvor funktionaliteten udføres - vi har placeret det konkrete kendskab i det delegerende objekt | |||
Klassediagram | |||
Generelt bliver klassediagrammet for denne konstruktion følgende: | |||
Figur 3: Klasse- diagrammet |
| ||
Objektet hvor hændelsen opstår, er en instans af Invoker, og det objekt der skal udføre funktionaliteten en instans af Receiver. | |||
Det delegerende objekt realiserer Command-interfacet, og vil i diagrammet ovenfor være en instans af ConCommand. Vi har kun vist én realisering af Command, men i en konkret anvendelse vil der naturligvis være flere, da det ellers ikke ville være muligt at udskifte én Command med en anden. | |||
Klientens rolle er at opbygge objektsystemet. Efter systemet er opbygget træder klienten kun til hvis Command-objektet skal udskiftes | |||
action-metoden repræsenterer en eller flere metoder | I klasse-diagrammet er der ikke nogen super-klasse til Receiver. Det skyldes at Receiver'ne kan være meget forskellige, og at det kun er Command-objektet, som ved hvad action-metoden er. Man skal derfor ikke tænke i baner af action-metoden som kommende fra et interface som Receiver-klasserne realiserer. action-metoden kan udemærket være flere metoder, og execute-metoden kan have adskillige kald af diverse metoder på Receiver'en for at implementere den ønskede kommando. Man kan her drage en analogi til begrebet "mere eller mindre adapter", som det kendes fra Adapter Pattern. | ||
Interaktion | |||
Forløbet i følgende sekvensdiagram er relativ kort, men skal alligevel læses i to dele: | |||
Figur 4: Inter- aktionen |
| ||
Først har vi opbygningen af systemet; hvor klienten laver en instans af ConCommand og giver den til Invoker-objektet. Forbindelsen fra ConCommand-objektet til Receiver-objektet etableres ved kaldet af ConCommand's konstruktor; hvilket ofte er bekvemt. | |||
På et senere tidspunkt opstår der en hændelse i Invoker'en og den sender en request til ConCommand-objektet, om at få udført funktionaliteten. ConCommand'en kalder videre til Receiver-objektet som gør det der skal gøres. | |||
Implementation | |||
Vi vil først lave en simpel implementation af det foregående. | |||
Først har vi Command-interfacet og realiseringen ConCommand: | |||
| |||
| |||
I det generaliserede eksempel er disse meget simple, men i praksis behøver execute ikke kun delegere metode-kaldet videre, á la Handle Pattern, men kan (som nævnt ovenfor) være mere over i Adapter Pattern, idet den gøre et større arbejde i forbindelse med anvendelsen af Receiver. | |||
Dernæste følger Invoker'en som ConCommand'en tilmeldes: | |||
| |||
Igen simpelt - en reference sættes og en metode kaldes via denne. | |||
Receiver'en er endnu mere simpel: | |||
| |||
Da dette ikke er et konkret eksempel har vi reelt ikke nogen funktionalitet, og klassen er derfor uhyre enkel. | |||
En test-anvendelse kunne være følgende: | |||
| |||
Eksempel: Menuer i Swing | |||
I forbindelse med menuer i Swing kan vi anvende Command Pattern. Vi kan gøre det generelt med følgende klasse-diagram, hvor klasserne er placeret så de afspejler klasserne i figur 3. | |||
Figur 5: Action- Listener som Command |
| ||
Her er JMenuItem Invoker'en og ActionListener er Command-interfacet. | |||
Idéen med at anvende Command Pattern kan bruges i forbindelse med alle Listener's, men den er specielt nærliggende med JMenuItem, da man nogle gange ønsker dynamisk at ændre, ikke alene hvilke menupunkter der optræder på en menu, men også hvad de gør. | |||
I vores eksempel vil vi ikke tage udgangspunkt i en bestemt applikation, men generelt vise hvordan man kan implementere ovenstående og dynamisk udskifte Command-objektet. | |||
Vi vil lave to subklasser til ActionListener, med konkret kendskab til hver deres Receiver-klasse. | |||
Den første benytter sig af en Receiver, der kan udskrive dags dato (og klokkeslet). Bemærk at Command'en får kendskab til Receiver'en gennem konstruktoren. | |||
| |||
| |||
Den anden Command gør selv et større arbejde, idet den anvender Receiver'eren fire gange, for at udskrive en tekst på fire linier: | |||
| |||
| |||
Ovenstående fire klasser er naturligvis meget enkle, men de viser hvordan Command-objekter bruger tilhørende Receiver-objekter. | |||
Vi anvender nu de to Command's som ActionListener's på et menu-punkter (dog kun én af gangen): | |||
| |||
Det første menu-punkt - Do something - har tilknyttet én ActionListener, der enten er en VorCommand1 eller en VorCommand2. Hver gang det andet menu-punkt - Swap Command - vælges, skifter vi mellem hvilken der er ActionListener. Med referencen current har vi hele tiden fat i den aktuelle ActionListener på det første menu-punkt. | |||
Figur 6: Action- Listener som Command |
| ||
Relationer til andre patterns | |||
Handle Pattern | Command Pattern ligner en hel del Handle Pattern. I Command Pattern er vi dog ikke så fokuserede på at flere, af hinanden uafhængige objekter, kan være kilde til de hændelser der skal udløse en funktionalitet. Det er mere klientens manglende rolle i forbindelse med udskiftningen, der interesserer os. I Command Pattern taler vi dog ikke om nogen egentlig klient, da vi også tager opbygningen af objektsystemet ind i billedet. | ||
Selv om vi ikke fokuserer så meget på flere objekter, som "hændelses-kilde" til samme funktionalitet, vil en sådan konstruktion fungere problemfrit i Command Pattern. F.eks. ser man ofte at et menu-punkt også findes som icon i en toolbar, så brugeren kan vælge at aktivere funktionaliteten med den hændelse han finder mest bekvem. | |||
Adapter Pattern | Command-objektets anvendelse af Receiver kan være mere end blot ét kald af en action-metode. Command-objektet kan realisere en funktionalitet ved at kalde flere forskellige metoder på Receiver'en, og på den måde fungere som en slags "mere adapter" | ||
Referencer | |||
[GoF94] s.233-242. | |||
[Grand98] s.277-287. | |||
| |||
Generelt eksempel:
| |||
Swing eksempel:
| |||