{ "title": "Chain of Responsability Pattern", "exercisesLink": "opgaver/opgaver.htm" }
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 udmæ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 Request'en om at løse opgaven sendes ned langs en linket liste af problemløsere. Når et objekt modtager request'en, skal den tage stilling til om den påtager sig opgaven. Hvis den vælger at tage sig af opgaven, sender den ikke request'en videre, men løser opgaven og returnerer. Hvis den ikke vil løse opgaven sender den request'en videre til det næste objekt i kæden.
CoR Dette design kalder man generelt Chain of Responsibility (CoRJeg bruger forkortelsen CoR for Chain of Responsibility. Man skal være opmærksom på at denne forkortelse ikke anvendes andre steder end i DocJava.). 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 multipliciteten 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 request'en 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 request'en.

 

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:
Source 1: Boolsk returnering fra konkret Handler-klasse
            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:
Source 2: Boolsk returnering fra default implementation
            public boolean handleRequest(...) {
              if ( next != null )
                return next.handleRequest(...);
              else
                return false;
            }
        
Om det reelt er nyttigt for klienten, at få denne viden, afhænger af den konkrete anvendelse.

 

Varianter

 

 

Decorator Pattern

Decorator Pattern har samme opbygning som CoR, idet det ligeledes har en kæde af objekter, der står til rådighed for klienten. Klienten kender, også her, kun det forreste objekt, og har ikke kendskab til den bagvedliggende kæde af objekter. I CoR er formålet med kæden, at delegere problemet til dét objekt der kan løse klientens problem — det er ikke formålet i Decorator Pattern! I Decorator Pattern vil alle objekter normalt deltage i håndteringen af klientens request. Her er tanken, at det sidste objekt har en løsning som det næst-sidste objekt anvender i sin løsning osv., indtil det forreste objekt laver den endelige løsning baseret på de andre objekters løsning. På den måde bliver ethvert objekt en variation af det efterfølgende objekt — det dekorerer det efterfølgende objekt!
Et meget simpelt eksempel kunne være et interface Pris:
Source 3: Component interface
          public interface Pris {
            public double getPris();
          }
        
En klasse der implementerer dette interface, skal kunne returnere en pris. Alle objekter i kæden skal implementere dette interface. I Decorator Pattern kalder man dette fælles interface: Component.
Det sidste objekt i kæden kunne være en instans af følgende implementation af Pris-interfacet:
Source 4: Concrete-Component
              public class SimpelPris implements Pris {
                private double pris;
                
                public SimpelPris(double pris) {
                  this.pris = pris;
                }
              
                @Override
                public double getPris() {
                  return pris;
                }
              }
            
Instanser af denne klasse har ikke nogen reference til et efterfølgende objekt, og kan derfor kun optræde som det sidste objekt i kæden. En sådan klasse kaldes i Decorator Patterm: ConcreteComponent.
Der kan være mange objekter i kæden før det sidste element. Vi vil indskrænke os til ét, der vil være en instans af følgende klasse:
Source 5: Decorator-klasse
              public class PrisMedMoms implements Pris {
                private Pris component;
                
                public PrisMedMoms(Pris component) {
                  this.component = component;
                }
                
                @Override
                public double getPris() {
                  return 1.25 * component.getPris();
                }
              }
            
Instanser af denne klasse har en reference til et efterfølgende objekt, og deres løsning er at lægge 25% til den pris som den modtager fra det efterfølgende objekt, når klienten kalder getPris-metoden.
Vi vil undlade at lave en egentlig klient-klasse, men i stedet opbygge kæden og anvende den fra main:
Source 6: Klient ved ikke at den anvender Decorator
          public class Main {

            public static void main(String[] args) {
              Pris pris = new PrisMedMoms(new SimpelPris(50.0));
              
              System.out.println(pris.getPris());
            }
          }
        
          62.5
        
Hvis vi her ser klienten som værende den sidste linie i main, der kalder getPris-metoden, så kender klienten ikke til kæden bestående af en Decorator med et efterfølgende ConcreteComponent — vi har dekoreret prisen med moms-egenskaben uden at klienten ved det!
Eksemplet med pris og moms er meget simpelt. Det er relativ enkelt at se Decorator Pattern, men til gengæld, er det ikke et godt eksempel. Det skyldes at SimpelPris og PrisMedMoms er meget enkle og ikke særlig forskellige klasser. Det betyder, at eksemplet ikke rigtig motiverer anvendelsen af Decorator Pattern.
Et langt bedre eksempel på Decorator Pattern finder man i anvendelsen af streams. Når vi arbejder med f.eks. filer, kan vi lave:
          BufferedReader input = new BufferedReader(new FileReader(filename));
        
Her er ConcreteComponent-klassen: FileReader, som bliver dekoreret af Decorator-klassen: BufferedReader.

 

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).