© 1999-2001, Flemming Koch Jensen
Alle rettigheder forbeholdt
Strategy Pattern

Opgaver

 

 

Motivation
Vi vil starte med at motivere dette design pattern med et eksempel.
Forskellige algoritmer I programmer til tekstbehandling, kan man normalt vælge forskellige måder hvorpå man kan få placeret teksten på en side - der tænkes her på venstre-/højrestillet og centreret tekst. Der skal bruges en forskellig algoritme til at beregne tegnenes placering på siden, alt efter hvilken indstilling der vælges.
Nemt skifte Vores design problem er: Hvordan vi kan gøre det nemt, at foretage skiftet mellem de forskellige algoritmer?
Klient vokser Eftersom det er en funktionalitet som klienten ønsker udført, vil det umiddelbart være naturligt at lade algoritmen være en metode i klientens klasse - specielt da det drejer sig om en opgaves løsning, der ofte vil kræve adgang til data som klienten allerede kender, og evt. selv har. Det er dog en stor ulempe for klienten, hvis den skal indholde implementationen af de forskellige algoritmer, da det let får klassen til at antage et betydeligt omfang. En løsning med if-sætninger der vælger mellem forskellige service-metoder alt efter hvilken "indstilling" der er valgt, er derfor ikke nogen god løsning - den skalerer dårligt!

 

Problem

Hvordan kan vi gøre det nemt for klienten, at den får udført skiftende algoritmer.

 

Løsning

Delegering Løsning er delegering. Klienten kalder en metode på et andet objekt, og er på den måde uvidendende om hvad der konkret sker. Samtidig er udskiftning af algoritmen, det samme som udskiftning af det objekt som klienten delegerer opgaven til.
Lad os se en eksempel, der viser teknikken.
Først laver vi et interface der erklærer den metode der implementerer algoritmen som vi vil variere.
Strategy.java
public interface Strategy {
  
  public void algorithm();
}
Vi laver dernæst to klasser, der implementerer dette interface:
ConcreteStrategyA.java
class ConcreteStrategyA implements Strategy {
  
  public void algorithm() {
    System.out.println( "ConcreteStrategyA.algoritme()" );
  }
}
ConcreteStrategyB.java
class ConcreteStrategyB implements Strategy {
  
  public void algorithm() {
    System.out.println( "ConcreteStrategyB.algoritme()" );
  }
}
Forskellen er naturligvis minimal, men det giver os noget at variere imellem, da vi nu kan skifte mellem de to implementationer.
Algoritmen bruges af en anden klasse vi kalder Context:
Context.java
public class Context {
  private Strategy strategy;
  
  public void setStrategy( Strategy strategy ) {
    this.strategy = strategy;
  }
  
  public void operation() {
    System.out.println( "Context.operation() - start" );
    
    strategy.algorithm();
    
    System.out.println( "Context.operation() - end" );
  }
}
Klassen har en metode: operation, der bruger algoritmen, og med setStrategy-metoden kan vi variere hvilken algoritme der bruges, ved at give den instanser af forskellige klasser.
Dette gør vi i testanvendelsen:
Main.java
public class Main {

  public static void main( String[] argv ) {
    Context context = new Context();
    
    context.setStrategy( new ConcreteStrategyA() );
    context.operation();
    
    context.setStrategy( new ConcreteStrategyB() );
    context.operation();
  }
}
Context.operation() - start
ConcreteStrategyA.algoritme()
Context.operation() - end
Context.operation() - start
ConcreteStrategyB.algoritme()
Context.operation() - end

 

Klassediagram

Lad os se klassediagrammet for Strategy Pattern:
Figur 1:
Klasse-diagrammet

Det er Context-klassen der har klient-rollen.

 

Interaktion

Lad os se sekvensdiagrammet, der beskriver testanvendelsen ovenfor:
Figur 2:
Interaktions-diagrammet

 

Implementation

Implementationen af er ukompliceret, pånær én ting, der til gengæld kan give problemer.
Som det allerede antyder under motivationen, er det ikke problemfrit at placere en funktionalitet i et andet objekt, som egentlig hører hjemme i et andet - det giver nemlig problemer med adgangen til data.
set/get-metoder Hvis algoritmen har brug for data, der optræder i datakernen af Context-objektet, har vi et problem. Problemet kan umiddelbart løses med set/get-metoder, men hvis algoritmen har et stort behov for data-adgang vil det belaste Context-klassens interface - specielt hvis den eneste motivation for set/get-metoderne er Strategy's behov.
Indre klasse En løsning kan derfor være at gøre Strategy-klasserne til indre klasser i Context-klassen. Denne løsning giver fri adgang til Context-klassens datakerne, men har den klare ulempe, at det ikke vil være muligt at indføre nye implementationer af algoritmen, med mindre man har kildeteksten til Context-klassen. I forbindelse med et framework vil det bestemt ikke være en hensigtsmæssig løsning.

 

Relationer til andre patterns

 

Template Method

Det problem som Strategy Pattern løser ved delegering, løser Template Method vha. nedarvning. I Template Method lader man algoritmen variere alt efter hvilken subklasse der er tale om, og en hook-metode implementerer algoritmen.