© 1999-2003, Flemming Koch Jensen
Alle rettigheder forbeholdt
CoR1 Pattern

Opgaver

 

Motivation
Klienten er som altid kendetegnet ved, at det er den der har problemet, og den ønsker at indgå i et objektsystem; hvor andre objekter løser det for den. I det simple design, har klienten en reference direkte til et sådant problemløsende objekt. I den situation er det fastlagt hvilket objekt der løser opgaven, og klienten er bevidst om dette objekt.
Figur 1:
Klient med kendskab til mange opgave-løsere
Kobling Hvis klienten har kendskab til flere objekter, som kan løse forskellige opgaver for klienten, skal klienten tage stilling til hvilket objekt den skal sende en konkret opgave til. Dette skaber en kobling mellem klient og objekter, og klienten skal indeholde kode, der afgør hvem der skal have opgaven.
Vi ønsker ikke at klienten designmæssigt skal bebyrdes med denne opgave, og ønsker en løsning hvor klienten blot stiller opgaven, og andre tager stilling til hvem der løser den. Det er altså ikke nok at andre objekter løser selve opgaven, det skal også være andre objekter der finder ud af hvem der skal løse opgaven.

 

Problem

Vi ønsker at en klient kan få løst en opgave uden selv at skulle afgøre hvilket af flere objekter, der skal løse opgaven.

 

Løsning

Den mest nærliggende løsning er at lave et container-objekt, der holder styr på alle opgaveløserne og tager stilling til hvem der skal løse opgaven:
Figur 2:
Container-objekt delegerer til opgave-løsere
Vi ser f.eks. her hvordan containeren delegerer en request videre til et af sine objekter.
  Det vi reelt har gjort, er at flytte en del af klienten, fra figur 1, ud i et nyt objekt. Et objekt der har ansvaret for at holde styr på, og delegere opgaver til en række objekter.
Bedre løsning Løsningen er for så vidt udemærket, men der findes blot et enklere og endnu bedre design. Den bedre løsning distribuerer beslutningsprocessen ud på den enkelte problemløser, som selv tager stilling til om netop dén vil/kan løse opgaven. En anden fordel er at vi fuldstændig slipper for container-klassen.
Lad os se den distribuerede løsning (vi indskrænker til tre problemløsende objekter afht. figurens bredde):
Figur 3:
Linket liste af objekter
Sende videre Requesten om at løse opgaven sendes ned langs en linket liste af problemløsere. Når et objekt modtager requesten, skal den tage stilling til om den påtager sig opgaven. Hvis den vælger at tage sig af opgaven, sender den ikke requesten videre, men løser opgaven og returnerer. Hvis den ikke vil løse opgaven sender den requesten videre til det næste objekt i kæden.
CoR Dette design kalder man generelt Chain of Responsibility. Chain, fordi det er en kæde af objekter. Responsibility, fordi de har et ansvar for løse opgaven.
Ingen garanti for løsning Der er ingen garanti for at noget objekt løser opgaven, de kan alle vælge at sende den videre. Det sidste objekt kan på samme måde som de andre vælge ikke at påtage sig opgaven, og da dens reference videre i listen er null, bliver den nød til at returnere. Man vil naturligvis ikke lave objekterne, så de uden grund lader opgaver gå forbi, så i forhold til container-løsningen er det ikke en forringelse, idet det med en container blot vil være den der konstaterer, at ingen af problemløserne kan løse opgaven. Forskellen er den samme: ingen af objekterne kan løse opgaven - spørgsmålet er blot hvor det konstateres.
Den linkede liste kan ses som en sti i et træ:
Figur 4:
Sti gennem omvendt træ, fra blad til rod
Man kunne kalde det et omvendt træ, fordi det er børnene der har referencer til deres forældre-knuder, og ikke omvendt. Med et træ kan flere forskellige linkede lister deles om problemløsere. En klient skal blot have fat i en knude i træet, og de problemløsere den har til rådighed vil være givet ved dens entydige sti, der fra til roden.

 

Klassediagram

  Lad os se det typiske klassediagram, idet vores problemløsende objekter generelt kaldes handlere:
Figur 5:
Klasse-diagrammet
Bemærk her kardinaliteten 1, for Handler til Handler. Dette sikrer/indikerer, at der er tale om en linket liste.
Vi har ladet Handler være en abstrakt klasse, frem for et interface, da det giver os to fordele, som begge vedrører next-referencen:
1 Vi kan lave en default-implementation af handleRequest-metoden. Default-implementationen sender altid requesten videre. Man vil dog altid override denne implementation, og den er kun lavet for at kunne bruges med et super-kald fra den overridende metode i subklassen - hvorfor?
  Ved at bruge et superkald i subklassen, kan vi helt undgå at tilgå next. Den kan derfor være private i superklassen, og vi behøver i subklassen ikke forholde os til, at vi er en del af en linket liste. Med andre ord: Det, at vi er en del af en linket liste, er indkapslet og skjult (information hiding) i superklassen.
2 Vi kan lave en konstruktor som sætter next-referencen, og på den måde undgå at subklassens tilsvarende konstruktor behøver at tilgå referencen. De kan blot nøjes med at kalde superklassens konstruktor, med den ønskede next som parameter.

 

Interaktion

  Betragt følgende sekvensdiagram:
Figur 6:
Request der håndteres af sidste objekt i kæden, eller gør den?
Her propagerer handleRequest-kaldet gennem den likede liste af to objekter. Vi kan se at det sidste objekt i listen returnerer, men vi kan ikke se ud fra diagrammet om den har løst opgaven, eller måtte melde pas.
  Hvis det første objekt løser opgaven er det derimod tydeligt ud fra sekvensdiagrammet:
Figur 7:
Request der håndteres før den når slutningen af kæden
Her når handleRequest-kaldet ikke længere end til det første objekt og det andet/sidste objekt hører aldrig om requesten.

 

Implementation

Implementationen er ukompliceret, som den er beskrevet under klassediagrammet ovenfor.
 

 

Boolsk returnering

  En nyttig lille ændring i den sædvanlige implementation af CoR Pattern, er at lade handleRequest returnere boolsk om opgaven rent faktisk blev løst. I så fald skal metoden i subklasserne implementeres som:
public boolean handleRequest(...) {
  if ( kan håndtere request ) {
    håndter request
    return true;
  } else
    return super.handleRequest(...);
}
  Og metoden i superklassen Handler, skal implementeres som:
public boolean handleRequest(...) {
  if ( next != null )
    return next.handleRequest(...);
  else
    return false;
}
Om det reelt er nyttigt for klienten opnå denne viden afhænger af den konkrete anvendelse.

 

Varianter

 

 

Decorator Pattern

  Decorator Pattern kan ses som en variant af CoR Pattern. Idéen i Decorator Pattern er at man nemt kan udvide med nye GUI lag, af komponenter der både kan bidrage grafisk og funktionelt. Hver af objekterne i listen afgør om de vil håndtere requesten ud fra om de områdemæssigt er ramt af et muse-klik.
I modsætning til CoR Pattern er det sidste objekt en bagstopper og betegnes GUI-mæssigt som desktop'en. Det betyder at ethvert muse-klik altid vil blive håndteret, om ikke andet så af desktop'en.

 

Relationer til andre patterns

 

 

Composite Pattern

  CoR Pattern kan ses som en slags en-dimensionelt Composite Pattern.
 
Composite Pattern i den forstand, at der er tale om en Composite, der foregiver at kunne løse opgaven, selv om den i virkeligheden er hovedet i en linket liste; hvis hale's hoved ligeledes foregiver at kunne løse problemet - osv. rekursivt gennem den linkede liste.
En-dimensionelt i den forstand, at der er tale om en linket liste i modsætning til et træ (i Composite Pattern).
 
fodnoter:
1 Jeg bruger forkortelsen CoR for Chain of Responsibility. Man skal være opmærksom på at denne forkortelse ikke anvendes andre steder end i DocJava.