© 1999-2003, Flemming
Koch Jensen
Alle rettigheder forbeholdt |
Handle Pattern |
Motivation Log I forbindelse med en applikation vi er ved at udvikle, ønsker vi at have et Log-objekt der kan bruges til at registrere en række hændelser, der indtræffer i løbet af program-afviklingen (se evt. kapitlet Polymorfi, hvor dette eksempel også optræder). Diverse formål På baggrund af disse registreringer vil vi kunne vurdere om applikationen fungerer tilfredsstillende i de pågælde situationer. En anden mulighed kunne være, at vi ønskede et kontrollere hvad applikationen havde foretaget sig under program-afviklingen for at sikre os, at dets brugere ikke har gjort noget de ikke må - f.eks. et program til en bank, der registrerer alle transaktioner på diverse konti. Behovene for at logge er mange-artede, og hvad det konkrete behov er, har ikke nogen betydning i det eksempel vi nu vil studere. Vi vil nemlig se hvordan man generelt får objekter til at arbejde sammen om denne opgave. Vores klient er et objekt der ønsker at registrere en hændelse. Til det formål associerer vi et Log-objekt: Figur 1:
Klient med Log-objekt
Her kalder klienten en registerEvent-metode på Log-objektet hver gang den ønsker at registrere en event. I praksis kan man håndtere de oplysninger som sendes til loggen på flere forskellige måder: Håndtere informationer forskelligt Man kan skrive dem ud på skærmen så en evt. udvikler eller systemadministrator kan holde øje med hvad der sker.
Man kan skrive dem ned i en file til senere læsning efter behov.
Man kan sende dem over nettet til en anden maskine hvorfra man kan overvåge hvad der sker.
Og der er sikkert flere andre muligheder end disse. For at understøtte forskellige ønsker til hvordan oplysningerne skal behandles, vælger vi derfor at lave et interface Log og lade dette have en realisering for hver måde, vi ønsker at kunne behandle informationerne på: Figur 2:
En realisering for hver måde at håndtere loggen på
Polymorf metode Med den polymorfe metode registerEvent, kan vi nu dynamisk ændre hvad der sker med de informationer som klienten ønsker at registrere i loggen. Dette gøres ved at udskifte det objekt som klienten refererer til med instanser af de forskellige subklasser. (Man kunne evt. finde det nyttigt at lade metoden returnere boolsk som det lykkedes at registrere informationerne i loggen (relevant for filer og netværk), men for eksemplets skyld har vi blot ladet returtypen være void) Vi har på sin vis fundet en rimelig måde at implementere dynamikken på - vi er nu i stand til at ændre hvad der sker med loggen mens programmet kører. Der er dog en lidt upraktisk bivirkning ved vores løsning: Antag at vi har flere/mange klienter som ønsker at logge hændelser. Figur 3:
Problem med flere klienter, der bruger samme Log-objekt
Her har vi tre klienter som ønsker at anvende det samme objekt - et ScreenLog-objekt, der udskriver til skærmen. Vi ønsker at kunne udskifte dette Log-objekt med et andet, f.eks. et FileLog-objekt, og vi ønsker at foretage denne udskiftning under programmets udførelse. Problemet ligger i, at det er meget upraktisk at skulle huske på hvem der anvender et objekt - f.eks. vores ScreenLog-objekt - og dernæst få samtlige klienter til at anvende et andet, når vi ønsker det. Upraktisk Ovenfor skal hver klient have en set-metode, så vi kan styrre hvilket Log-objekt den anvender. Når vi ønsker at klienterne skal bruge et andet Log-objekt, skal vi kalde denne metode på samtlige klienter, og først når vi har kaldt set-metoden på den sidste af klienterne er udskiftningen tilendebragt. En upraktisk manøvre!
Problem
Udskifte objekt Vi ønsker dynamisk at kunne udskifte et objekt som er associeret en klient, uden at involvere klienten i denne udskiftningsoperation. Mere konkret betyder dette, at vi ønsker at kunne udskifte objektet med et andet, uden at skulle ændre den reference som klienten har til objektet.
Løsning
Delegering Løsningen skal findes i delegering. Når klienterne ønsker at logge informationer, skal de ikke tale til Log-objektet, men til et andet objekt - et objekt som aldrig skal udskiftes. Dette objekt taler dernæst til Log-objektet, som klienterne reelt ikke kender. Samme interface At klienterne reelt ikke kender Log-objektet ser vi dog ingen grund til at de skal vide eller tage hensyn til. Derfor lader vi det delegerende objekt have samme interface som Log-objektet, så klienterne ikke mærker nogen forskel. Opbygningen i figur 3 ændres nu til at være følgende: Figur 4:
Delegerende objekt
Set-metode Nok siger vi, at Handle-objektet har samme interface som det objekt det delegere til, men den har dog behov for én metode mere, nemlig en set-metode til den reference som den har til Body-objektet. Ved at kalde denne metode kan andre objekter styrre hvilket objekt samtlige klienter anvender.
Klassediagram
Figur 5:
Betegnelser i Handle Pattern
"Samme" signatur Det er ikke afgørende i Handle Pattern at Handle's metode og Body's metode hedder det samme. Men man taler dog om, at metodernes signatur er den samme, selvom man skulle fravige dette vedrørende metode-navnet
Interaktion
I følgende sekvensdiagram kan man følge delegeringen af et metode-kald fra klienten til "objektet", som den regner med løser opgaven: Figur 6:
Klienten anvender implemen-tationen
Som man ser er delegeringen ganske triviel, idet Handle ikke foretager sig andet en at formidle kaldet, uden selv at bidrage til funktionaliteten. I følgende samarbejdsdiagram er det illustreret hvordan Body-objektet udskiftes: Figur 7:
Udskiftning af Body-objektet
Et eller andet objekt - her kaldet Admin - ønsker at udskifte Body-objektet. Admin har en instans af ConBody2, som den ønsker skal erstatte den instans af ConBody1 som klienten i øjeblikket anvender (indirekte). Man ser at udskiftningen fuldstændig forbigår klienten, idet Admin kun henvender sig til Handle-objektet.
Implementation
Om Body laves som et interface eller en abstract class, afhænger alene af nytteværdien af fælles implementation i super-klassen. Fælles super-klasse Hvis signaturerne i Handle og Body er præcis de samme (og typisk når Body er et interface, men ikke nødvendigvis) kan man vælge at lade Handle nedarve fra Body. Figur 8:
Fælles interface
Mulig fejl Denne løsning har dog den ulempe, at en senere vedligeholder kan blive forvirret og evt. lade klienten arbejde direkte med instanser af ConBody1 og ConBody2. Løsningen i det oprindelige klassediagram, i figur 5, forhindrer dette.
Bridge Pattern
Bridge Pattern Som nævnt, lige under dette kapitels overskrift, kalder man også Handle Pattern for Bridge Pattern. I den forbindelse betegner man Handle som Abstraction og Body kaldes Implementor. Om man anvender betegnelserne fra Handle Pattern eller Bridge Pattern er uden betydning - der er ingen anden forskel mellem de to.
Relationer til andre patterns
Handle Pattern kan ses som et special-tilfælde af Adapter Pattern, og vil i den sammenhæng blive betegnet som en "meget lidt Adapter".
Referencer
[GoF94] s.151-161. [Grand98] s.191-203. Handle Pattern introduceres i kapitlet Komposition, som et eksempel på indirection.