Dette er en DRAFT tutorial, hvilket vil sige at der ikke er lavet hård-korrektur. Du kan derfor selv være med til at gøre opmærksom på kryptiske formuleringer, stavefejl eller andet. Vi sætter pris på din hjælp.

Preface

Dette er en artikel om brug af design patterns. Der vil i artiklen blive brugt UML til visuelt at modellere sammenhænge mellem objekter. De konkrete kodeeksempler vil være i ActionScript 3.0, men alle eksempler og teorien vil direkte kunne bruges i andre versioner af ActionScript eller et andet objekt orienteret (OO) programmeringssprog. Jeg vil i artiklen ikke bruge tid på at forklare basis OO begreberne (arv/inheritance, indkapsling/encapsulation, grænseflade/interface, abstrakte datatyper m.m.), da du kan læse om disse begreber i artiklerne her:

Introduktion

I denne artikel skal vi se nærmere på et af de mest basale design patterns; nemlig strategy pattern. Selvom strategy betegnes som basalt, er det på ingen måde et uvæsentligt pattern. Grunden til at det betegnes som et basalt pattern, er at det omhandler de mest basale begreber og principper når vi snakker fornuftigt OO deisgn. Først vil vi se på den helt formelle definition af strategy og så gennem artiklen prøve at skille denne beskrivelse ad, og gøre den mere forståelig og omsættelig til design i koden.

Problemstillingen

Vi har netop fået et spændende spilprojekt ind af døren, fra en stor og forhåbentligt vedblivende kunde. Opgaven går ud på at vi skal lave et roll play game (RPG) hvor brugeren til at starte med har mulighed for at vælge mellem 3 forskellige avatare. Den er kundens idé at der løbende skal udvikles og tilføjes nye avatare som brugerne kan vælge imellem. De 3 avatare der skal laves i startversionen er ”Troll”, ”Knight” og ”Goblin”.

Lidt analyse

Vi kaster os straks over kundens opgave, og ved at de bedste resultater kommer ved først at se projektet lidt fra oven. Vi tager derfor vores analyse hat på, og forsøger at huske tilbage på hvad det nu er der er smartest at gøre i en situation som denne. Vores umiddelbare løsning bliver at finde fællestrækkene ved de 3 avatare og samle det i én AbstractAvatar klasse, som så alle vores avatare kan nedarve fra. Samtidig tænker vi også at denne løsning er holdbar i forhold til nye avatare, som jo blot kan nedarve fra denne klasse også.

Til start har vi fundet ud af at alle avatare har en ”walk” metode, som man bruge til at flytte avataren rundt med, samt at alle avatare skal have en ”health” attribut til at gemme sit stadig for livsenergi, samt en ”strength” attribut til at gemme styrke (der kunne findes mange flere, men de er i eksemplet her, ikke taget med). Hver konkrete avatar klasse har en ”display” metode, tegner den grafiske avatar på skærmen.
Set i UML tegner vi det således:

Class diagram af inheritance forholdet mellem konkrete avatare og AbstractAvatar

Money in the bank. Eller...

Det var ”peice of cake” tænker vi, og kan allerede se de mange nuller på den fede check som kunden har lovet os. Mens vi sidder der og drømmer, kommer vi pludseligt i tanke om at kunden også havde nævnt noget med at hver avatar skulle udstyres med et våben, og en måde til at bruge våbnet på. ”Ingen problem” tænker vi ”- den kan jo puttes i AbstractAvatar klassen, så alle avatare får den!”. Vi kaster os derfor straks over at få lavet, hvad vi tror, er en lille let ændring og begynder at skrive en ”useWeapon()” metode i AbstractAvatar klassen.

Midt i denne proces slår det os, at hver har avatar sit eget våben: ”Troll” har en hammer, ”Knight” et sværd og ”Goblin” en kniv. ”Put med det, med et par ’if’ sætninger her og der, kan vores useWeapon metode sagtens bruges”, tænker vi. Så vi opretter en attribut ved navn ”weapon” og laver vores ”useWeapon” metode færdig.
Vores AbstractAvater ser i kode således ud:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package
{  
  public class AbstractAvatar
  {
    /**
    * The type of veapon the avatar has
    */   
    private var _weapon:String;
   
    /**
    * The current state of health in the avatar
    */   
    private var _health:int;
   
    /**
    * The strength of the avatar
    */   
    private var _strenght:int;
   
    /**
     * Models a new instance of AbstractAvatar:
     * NOTE: This is an abstract class and no instances
     * should be created directly from this class
     *
     */  
    public function AbstractAvatar()
    {
      //Abstrac constructor.
    }
   
    /**
     * Moves the avatar to a new coordinate in space.
     *
     * @param x
     * @param y
     *
     */  
    public function walk(x:int,y:int):void
    {
      //implementation not showen
    }
   
    /**
     * Starts the use of the avatars weapon
     *
     */  
    public function useWeapon():void
    {
      //The knight's sword use:
      if(_weapon == "sword")
      {
        //Here the knight swings his sword
      }
      //The Trolls hammer use:
      else if(_weapon == "hammer")
      {
        //Here the Troll gogs his hammer
      }
      //The Goblins knife use:
      else if(_weapon == "knife")
      {
        //Here the Goblin slices with his knife
      }
    }
   
    //***********************************************************
    //        GETTER / SETTER FUNCTIONS:
    //***********************************************************
   
    //...
    //Not showen in this example...
    //...
   
  }
}

Selvom vi godt kan se at det måske ikke er den smukkeste løsning, er vi alligevel godt tilfredse og mener at vi har fundet en fornuftig struktur på vores problem.

Ændringer er det eneste der aldrig ændre sig

Samme dag ringer kunden og fortælle at de allerede har fået ideer til 2 nye avatare, som de gerne vil have sat ind i spillet med det samme. Det drejer sig om en Faun og en Elve avatar. Faunen skal have bue og pil som våben og Elve avataren skal bruge samme sværd som Knight.
Selvom om det kun betyder én ekstra ”if” sætning i vores AbstractAvatars ”useWeapon” metode, kan vi godt se at det alligevel ikke er en helt god løsning vi har fat i. Hvis vi konstant skal åbne op i AbstractAvatar hver gang der kommer en ny avatar bryder vi jo med alt hvad vi har lært om OO, nemlig at vi skal indkapsle det der ændre sig.

Så hvordan kan vi indkapsle det der ændre sig i vores problem. Det der ændre sig er vores ”useWeapon” metode, så i stedet for at have den som en konkret metode i AbstractAvatar klassen, kunne vi indkapsle metoden i den enkelte avatar. Vi kaster os straks over en sådan løsning, og udarbejder et interface ”IWeaponBehavior” som beskriver en ”useVeapon” metode. På den måde ladet vi alle vores avatare implementere vores ”IWeaponBehavior” interface, så vi sikre os at de hver især implementere en ”useWeapon” metode.
Vores klasse diagram ser nu sådan her ud:

Selvom vi har fået ansvaret for ”useWeapon” ned i vores konkrete avatare, er vi ikke helt tilfredse. Det er noget der nager os. Vi har ved Knight og Elve skulle skrive præcis samme metode da de begge bruger sværd som våben. Samtid vil vi skulle sidde og skrive ”useWeapon” metoder på alle nye avatare der kommer til spillet, endda selvom de bruger samme våben som nogen af de andre.
Overblikket over hvilke våben der er i spillet er også sværere at holde styr på. Så vores problem med at skulle ændre i AbstractAvatar er nu flyttet over til at være et problem med at skulle skrive den samme kode igen og igen.
Samtidig har vi også hørt et rygte om at de på sigt skal være muligt at skifte våben mens en avatar er i spil. Så f.eks. ”Knight” skifter fra sværd til bue og pil. -> og det er vores nuværende opbygning ikke særlig fleksible over for. Det ville betyde en frygtelig masse ”if” sætninger igen.

En mere strategisk løsning

I stedet må finde en ny løsning. En der er holdbar i forhold til tilføjelser og ændringer. I stedet vil vi prøve at flytte det der ændre sig helt ud af vores nuværende kode, og få det indkapslet. Det vil i vores problem betyde at vi skal have ”useWeapon” helt ud af vores kode, da det er den der bliver ved med at ændre sig. Så i stedet skal vi have ”useWeapon” sat i et system, som vores avatar klasser kan bruge. Og fordi vi ved at der i fremtiden både kommer flere avatare og at de skal kunne skifte våben, skal vores ”useWeapon” system gerne kunne bruges dynamisk og fleksibelt.

Hvis vi holder fast i tanken om vores avatar har et våben, men at adfærden af dette våben ikke kendes af avataren. Altså på godt dansk, vores avatarer ved at de har et våben, men de ved ikke hvilket våben det er. Det eneste de behøver at vide er at de kan kalde en ”useWeapon” metode på deres våben. Det er de første tanker på vej mod at få vores våben indkapslet, og implementationen skjult fra omverdenen, som i det her tilfælde er avatarene.

En praktisk løsning

Rent praktisk kan vi holde fast i vores ”IWeaponBehavior” interface, men i stedet for at lade vores avatare implementere det, bygger vi nu i stedet en familie af våbenadfærd, som alle implementere dette interface. I vores AbstractAvatar ændre vi vores ”weapon” attribut til ”weaponBehavior” som nu i stedet for at holde navnet på det våben vi bruger i stedet indeholder et ”IWeaponBehavior” objekt. Fordi vi bruger et interface, behøver vi ikke vide om det er et sværd, en kniv eller en hammer vi har fat i, men kan i stedet nøjes med at fokusere på at det er en våbenadfærd som har en metode der er ens for alle våben.
Vores UML diagram ser herefter således ud:

Herunder er vist et eksempel på AbstractAvatar, en konkret avatar Knight, IWeaponBehavior interfacet og en konkret våben klasse. Samtidig er vist hvordan en RollPlayGame klasse ville kunne instantisere klasserne, og hvordan Knight objektet kan bruge Sword, uden at vide det er et sværd.

Læg mærke til abstraktionen der er i instantiseringen af objekterne i main klassen. Det er denne abstraktion vi er ude efter at kunne have over alt i vores kode ( var avatar:AbstractAvatar = new Knight(); ).

AbstractAvatar Klassen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package
{
  import IWeaponBehavior;
  
  public class AbstractAvatar
  {
    /**
    * The type of weaponbehavior the avatar uses
    */    
    private var _weaponBehavior:IWeaponBehavior;
    
    /**
    * The current state of health in the avatar 
    */    
    private var _health:int;
    
    /**
    * The strength of the avatar 
    */    
    private var _strenght:int;
    
    /**
     * Models a new instance of AbstractAvatar:
     * NOTE: This is an abstract class and no instances
     * should be created directly from this class
     * 
     */   
    public function AbstractAvatar()
    {
      //Abstrac constructor.
    }
    
    /**
     * Moves the avatar to a new coordinate in space.
     * 
     * @param x
     * @param y
     * 
     */   
    public function walk(x:int,y:int):void
    {
      //implementation not showen
    }
    
    /**
     * Starts the use of the avatars weapon
     * 
     */   
    public function fight():void
    {
      _weaponBehavior.useWeapon();
    }
    
    //***********************************************************
    //        GETTER / SETTER FUNCTIONS:
    //***********************************************************
    /**
     * Registers a new veapon behavior for the avatar. 
     * @param vb
     * 
     */   
    public function setWeaponBehavior(vb:IWeaponBehavior):void
    {
      _weaponBehavior = vb;
    }
    
    //...
    //Not showen in this example... 
    //...
  }
}

Knight klassen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package
{
  public class Knight extends AbstractAvatar
  {
    /**
     *Models a new instance of Knight 
     * 
     */   
    public function Knight()
    {
      super();
    }
    
    /**
     * Displays the knight on the screen
     * 
     */   
    public function display():void
    {
      //Displays a strong knight on the screen
    }
    
  }
}

IWeaponBehavior Interfacet:

1
2
3
4
5
6
7
8
9
10
11
package
{
  public interface IWeaponBehavior
  {
    /**
     * Defines a generic method for using a weapon 
     * 
     */   
    function useWeapon():void;
  }
}

SwordBehavior klassen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package
{
  public class SwordBehavior implements IWeaponBehavior
  {
    /**
     * Models a new SwordBehavior 
     * 
     */   
    public function SwordBehavior()
    {
      //Not Showen
    }
    
    /**
     * Implementet from IWeaponBehavior
     * Defines the swords use as a weapon 
     * 
     */   
    public function useWeapon():void
    {
      //Swinging the sword
      //...
    }
  }
}

RollPlayGame Main Klassen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package
{
  import flash.display.Sprite;
 
  public class RollPlayGame extends Sprite
  {
    public function RollPlayGame()
    {
      //Creates a new AbstractAvatar - as a concrete knight
      var avatar:AbstractAvatar = new Knight();
      
      //Creates a new IWeaponBehavior as a concrete SwordBehavior
      var weapon:IWeaponBehavior = new SwordBehavior();
      
      //Registers weapon with avatar:
      avatar.setWeaponBehavior(weapon);
      
      //Start avatars figth:
      avatar.fight();
      
    }
  }
}

Summasumarum

I vores AbstractAvatar har vi nu lavet en metode kaldet ”fight” som kalder ”useWaepon” på den attribut vi gemmer et ”IWeaponBehavior” objekt i. På den måde holder vi den egentlige implementation af de forskellige våben skjult for klienten, altså vores avatare, og vi kan tilføje nye våben til, og lade avatarene bruge dem, uden at de behøver at kende dem. Samtidig behøver vi kun skrive et givent våbens adfærd én gang, og kan genbruge våbenet i mange forskellige klasser. Den største fleksibilitet er at vi i runtime, altså når spillet er i gang, kan skrifte om på våbnene, uden at det gør nogen forskel. Vores AbstractAvatar har en ”setWeaponBehavior” metode, hvor vi kan sætte et nyt våben. Og fordi dette nye våben også er en ”IWeaponBehavior” type, ved vores AbstractAvatar allerede hvordan det bruges.

Essensen

Det er essensen af strategy pattern. At ensarte en række metoder eller udførelser, og kun lade klienten kende til det abstrakte (i vores tilfælde er det abstrakte IWeaponBehavior interfacet).

Vi kan nu skrifte våben runtime, tilføje avatare, genbruge våben mellem forskellige avatare og ikke mindst genbruge vores våben klasser, til andre projekter, uden at det behøver at være avatare der bruger dem. De er meget svage bindinger mellem de to klassehierarkier, og vores adfærd af det enkelte våben er indkapslet og skjult.

Den formelle beskrivelse

Den formelle beskrivelse af strategy pattern er sådan her:
Strategy pattern definere familiære algoritmer, indkapsler hver af dem og gør dem enstydige/ombyttelige. Strategy pattern lader algoritmer variere uafhængigt fra klienten der bruger dem.

Post Scriptum

Jeg håber du kan se anvendelsen af dette pattern. Det kan være et hav af forskellige algoritmer man ønsker at indkapsle og bruge enstydigt. Det behøver ikke være våbenadfærd, men kunne lige så vel være alt muligt andet fra menunavigation, til indkøbskurvs beregninger.