© 1999-2001, Flemming Koch Jensen
Alle rettigheder forbeholdt
Template Method

Opgaver

"Indsæt dit citat her."

- Bjarne Stroustrup

 

 

Motivation
Variation Nedarvning er en sproglig konstruktion, der giver os mulighed for at lave variation. Ved at lave forskellige subklasser til den samme super-klasse, kan vi variere de egenskaber, som er defineret i super-klassen, og evt. føje nye til.
Metodes implementation Ønsket om at have en grundlæggende definition som man kan lave variationer ud fra indkrænker sig ikke kun til klasser. Har vi f.eks. en metode, kan vi ønske at den tilsvarende metode i subklassen ikke blot er den nedarvede, men at den gør noget andet. I den forbindelse er vi generelt indskrænket til at override metoden og helt lave vores egen1. I visse tilfælde kan vi dog have nytte af super-klassens implementation af metoden, hvis det drejer sig om en udvidelse af funktionaliteten. I så fald kan vi lave et super-kald af super-klassens metode, et passende sted i den metode der overrides.
Overriding er primitivt Generelt er overriding en primitiv måde at lave variation af implementation, da den ofte vil betyde at vi får vanskeligt ved at genbruge den nedarvede implementation. Det skyldes, at vi med et super-kald ikke kan vælge hvad vi vil genbruge, men må tage det hele. Vi har brug for noget der er mere fleksibelt!
Generelt Sådan som problemet her er præsenteret er der talt meget om klasser og metoder, men vi kan formulere problemstillingen mere generelt.
Variere dele af algoritme En implementation af en metode er en algoritme, og det vi søger er en måde at kunne variere implementationen af denne algoritme. Vi søger ikke en alternativ implementation, som når man vælger mellem en boble-sortering og en quick-sort. Det vi søger er variation af en allerede fastlagt algoritme. Det betyder at det kun er dele af en algoritme vi ønsker skal kunne ændre sig, men den grundlæggende forbliver den samme. Hvis man f.eks. har en implementation af quick-sort, kunne man ønske sig at variere hvordan partitioneringen foretages, men at fasthold resten af algoritmen.

 

Problem

Vi ønsker at kunne variere dele af implementationen af en algoritme ved nedarvning, idet super-klassens implementation skal være en skabelon, hvor subklasserne udfylder de huller i implementationen som super-klassen har ladet stå åbne.

 

Løsning

Polymorfi Man kunne kort sige at løsningen er polymorfi, men lad os se et simpelt eksempel der illustrerer teknikken:
Vi har to klasser A og B; hvor der i A erklæres en algoritme, vi ønsker at fuldende i subklassen B:
Template-metode Lad os først se A-klassen, der har template-metoden - dvs. metoden, der har huller i implementationen, som subklasser skal udfylde. På dansk kan man kalde en template: en skabelon, men da betegnelsen: template, er meget udbredt på engelsk, vil vi holde til den:
Kildetekst 1:
Super-klassen med template-metoden
A.java
public abstract class A {
  
  public void algoritme() {
    System.out.println( "[A] Udfører start af algoritme" );
    
    delAlgoritme();
    
    System.out.println( "[A] Udfører slutning af algoritme" );
  }
  
  public abstract void delAlgoritme();
}
Det centrale i løsningen er naturligvis, hvad det konkret vil sige, at der er huller i implementationen, og hvordan de kan udfyldes.
Hullet Et hul, er et kald af en metode der ikke er implementeret - i dette tilfælde metoden: delAlgoritme. algoritme-metoden implementerer i hovedtræk algoritmen, men overlader det til subklasser at implementere en del af den - i dette tilfælde implementationen, der skal optræde mellem de to udskrifter.
Hook-metode Udfyldning af hullerne i algoritmen - i dette tilfælde kun ét hul - sker ved at subklasser implementerer hook-metoden: delAlgoritme. Denne metode kaldes en hook-metode, da man kan "hænge" de manglende stykker implementation "op på dem".
Idet vi stadig holder eksemplet meget enkelt, kunne subklassen: B, implementere hook-metoden med:
Kildetekst 2:
Subklasse der udfylder template ved at implementere hook-metode
B.java
public class B extends A {
  
  public void delAlgoritme() {
    System.out.println( "[B] Udfører delalgoritme" );
  }
}
Main.java
public class Main {

  public static void main( String[] argv ) {
    
    A a = new B();
    
    a.algoritme();
  }
}
[A] Udfører start af algoritme
[B] Udfører delalgoritme
[A] Udfører slutning af algoritme
I udskriften ser vi hvordan hook-metoden bliver udført som en del at algoritme-metoden, og at vi derved har udfyldt det hul der var i template-metoden's implementation.

 

Klassediagram

Lad os se det generelle klassediagram for Template Pattern:
Figur 1:
Klasse-diagrammet
Betegnelsen "hook-metode" Hvis det skal være helt korrekt, gælder betegnelsen hook-metode kun hvis super-klassen stiller en default-implemenation til rådighed. Da man ikke har et tilsvarende betegnelse for metoden når den er abstrakt, er det ikke ualmindeligt, at man alligevel kalder den en hook-metode (Denne praksis skyldes sikkert også at betegnelsen "hook-metode" lyder lidt "cool").

 

Interaktion

Interaktionen er her medtaget for fuldstændighedens skyld - den er meget simpel, og gentager blot hvad vi tidligere har beskrevet:
Figur 2:
Interaktions-diagrammet

 

Implementation

Tilgængelighed af hook-metoden I forbindelse med implementationen, er der kun et enkelt interessant spørgsmål, nemlig tilgængeligheden af hook-metoden. I forhold til klienten ønsker vi ikke at den skal kende hook-metoden, endsige kunne kalde den. I et sprog som C++ er det enkelt at realisere denne information hiding i form af en protected funktion, men i Java betyder protected som bekendt noget andet end i C++, og det er derfor ikke så virkningsfuldt. Det nærmeste man kan komme det er at placere AbstractClass og ConcreteClass i en package, men det er ikke altid en bekvem løsning.

 

Template method i Java

update og paint Template Method anvendes i forbindelse med tegning af grafiske komponenter i AWT/Swing. Alle grafiske komponenter implementerer en hook-metode: paint, der tegner komponentet. Når man kalder repaint på et komponent, kalder den update-metoden, og denne er en template-metode. update-metoden har for Container'e følgende opbygning (dette er en simplificeret udgave):
public void update( Graphics g ) {
  if ( isShowing() ) {
    g.clearRect( 0, 0, width, height );
    paint( g );
  }
}
Sletter baggrunden if-sætningen kontrollerer om komponentet overhovedet skal tegnes - om det er synligt. clearRect sletter baggrunden, og endelig har vi kaldet af paint, der som nævnt er en hook-metode som alle subklasser implementerer.
For almindelige komponenter, der ikke er Container'e er update-metoden endnu mere simpel, idet baggrunden ikke slettes.

 

Relationer til andre patterns

 

Strategy Pattern

I stedet for at bruge nedarvning til at variere en algoritme, bruger Strategy Pattern delegering til at variere hele den pågældende algoritme. I sin rene form er Strategy Pattern derfor mere sammenlignelig med variation med overriding, der blev nævnt i indledningen til dette kapitel. Ved at lade hook-metoden være en metode i et andet objekt, kan Strategy Pattern dog ses som et direkte alternativ til Template Pattern - et alternativ der gør det muligt dynamisk at ændre dele (eller det hele) af en algoritme.

fodnoter:
1
Denne begrænsning gør sig primært gældende i objektorienterede sprog fra C++-sprogstammen, men også hos andre sprog. Et sprog som f.eks. Beta, har bedre muligheder for at specialisere metoder.