Pre Script

Denne artikel er foranlediget af det netop overståede DFUG møde i Århus, hvor Martin Bjeld og jeg selv holdt et oplæg omkring strukturering og opbygning af et projekt, samt videre så på hvordan nogle enkelte elementer var sat sammen. I oplæget blev der blandt andet diskuteret interfaces i flash, og på baggrund af et generelt ønske om en indføring i begrebet, er følgende udarbejdet.

Indledning

De fleste er sikkert bekendte med begrebet eller termen ”interface”. For mange ledes tankerne straks over til en applikation eller webside interface betyder de knapper, bokse, tekstfelter, m.m. som siden eller applikationen viser. Men hvordan interface begrebet gør sig i forhold til programmering er, for mange, en mere ukendt sag. Det håber jeg på at kunne rede bod på inden du har læst dette færdigt.

Hvad betyder Interface?

For at få noget på det rene, så vi taler samme sprog, vil jeg lige starte med at få defineret hvad jeg forstår ved ordet ”interface”. Interface betyder ”grænseflade” eller ”snitflade” og netop grænseflade er et godt ord at huske på, når man forsøger at sætte sig ind i brugen og betydningen af interfaces.
Som nævnt i indledningen tænker de fleste på noget grafisk når vi snakker grænseflade, men det er kun fordi vi er holdt op med at kalde tingene ved deres fulde navn. For egentligt burde vi tale om en ”grafisk” grænseflade, eller måske endnu mere præcist en ”grafisk brugergrænseflade” når vi taler om websites og applikationer. Grunden til at jeg hæfter fokus omkring ordet ”grænseflade” er for at kunne bruge din forståelse af en grafisk brugergrænseflade, og så overføre den til programmering. En grænseflade, er altså en flade der grænser op mod noget andet… f.eks. en grafisk flade der grænser op mod en bruger. (f.eks. et website)
Med det defineret kan vi hoppe videre til at se hvordan en sådan grænseflade bruges i programmering.

Objekter har også grænser

Når vi taler objekt orienteret programmering (herefter OOP) må vi først forstå hvad et objekt er, for rigtigt at kunne forstå hvordan et interface gør sig gældende i forhold til dataobjekter. Faktisk vil jeg mene at vi er nød til at lade være med at gøre tingene avancerede for at kunne forstå det bedst. For det at programmere simpelt og logisk er i virkeligheden det som OOP handler om. OOP er ikke skabt fordi det er hurtigere eller lettere for en processor at eksekvere, eller fordi det fylder mindre i hukommelsen. Nej OOP er lavet for at gøre det lettere for os mennesker, at forstå hvad der sker i computere, og for at kunne udvikle ”ting” i et digitalt miljø, der ligner vores egen verden. Husk derfor på at OOP er til for at gøre det lettere for dig som programmør, og ikke til for at gøre det mere besværligt! Nu skrev jeg ”ting” før, men et mere retmæssigt ord er ”objekter” men betydningen er for så vidt den sammen… selvom Ting Orienteret Programmering ikke lyder nær så smart som OOP ? OOP handler om at skabe objekter der er sammensat som én helhed og på den måde definere dem.

Alt i Action Script er objekter og et hvert objekt er beskrevet i en såkaldt klasse (den klassificere objektet). Når du f.eks. laver et MovieClip så laver du et objekt af typen MovieClip, som er defineret i klassen MovieClip, og som nu findes i et digitalt univers i din applikation. Prøv at forestille dig en firkantet kasse, hvor en af siderne er farvet og de andre er lavet af glas. Sådan kan du se dit MovieClip. Det du kan se af dit MovieClip er den farvede side af kassen, og den repræsentere den grafiske grænseflade af MoveiClippet. Hov der var den igen: ”grænseflade”. – men hvad så med de sider af MoviClippet du ikke kan se? – ja, selvom du ikke kan se de øvrige grænseflader så er de der jo stadigvæk og det er disse der er beskrevet i selve klassen. Selvom du ikke kan se ”_x” på MovieClippet så er den der jo stadigvæk. Den er blot på MovieClippets kodemæssige grænseflade.

Og det at se og forstå at objekter i koden også har grænseflader, er i virkeligheden hele humlen af brugen af Interfaces. Det er jo langt fra alle objekter der har grafiske grænseflader. F.eks. har en klasse som Sound ingen grafisk grænseflade, og kan altså derfor ses som en kasse med alle siderne gennemsigtige. Men den har stadig en grænseflade.

Et eksempel er et ur. Den har en grafisk grænseflade, jeg kan kigge hvad klokken er, og jeg kan stille på viserne hvis jeg vil. Men jeg kan ikke se hvad der sker inde bagved.

Når Objekter Chatter

Vi er godt klar over at vi skal bruge en grafisk grænseflade når vi skal fortælle google at vi vil søge på noget givent, eller sige til vores mobiltelefon at den skal vise nummeret på en ven. Men sådan har objekter det også når de skal tale med hinanden. De bruger også grænseflader til at tale/kommunikere med hinanden. De behøver dog ikke nogen grafisk grænseflade. For at blive i eksemplet fra før med uret, så behøver uret ikke at være ét objekt men kunne være som en applikation. Viserne og urskiven er den grafiske grænseflade og knappen vi kan stille viserne på, er applikationens funktionalitet som en menu på en webside. Men lad os sige at viserne er et objekt for sig, batteriet er et objekt for sig, og knappen vi kan stille uret på er også et objekt for sig. Og for at simplificere eksemplet, så siger vi at det er batteriet der internt styrer viserne. Når jeg skruer på knappen så fortæller den viserne at de skal stille sig, og hver sekund siger batteriet til viserne at de skal flytte sig. Men hvordan kan vi sikre os at både knappen og batteriet kan tale med viserne. Det kan vi ved at definere en grænseflade som de kan bruge. Og det er præcist det samme vi gør når vi laver en klasse i Action Script og definere at denne klasse har en metode der kan et eller andet.

Med hvad ER interfaces så?

Selvom vi kan definere et objekts grænseflade i klasse definitionen, kan vi ofte komme i en situation hvor vi har brug for at lave en grænseflade der er fælles for flere forskellige objekter. Lad os vende tilbage til uret. Lad os sige at viserne er tre forskellige objekter af datatyperne SekundViser, MinutViser og TimeViser. Vi kunne godt lave vores batteri objekt så det kunne kende de tre datatyper, men det ville være omfattende da vi på den måde altid skulle lave vores batteri objekt i stand til at informere de forskellige muligheder der måtte være af visere. Hvad nu hvis vi pludselig en dags viser, en måneds viser og en dato viser i også… så skal vores batteri objekt laves endnu mere omfattende fordi vi skal definere den til at kende alle disse forskellige datatyper. Det kan vi undgå ved at bruge et Interface. I et Interface kan vi nemlig definere en grænseflade som alle objekter der implementere dette interface med sikkerhed har. Det vil sige at vi kan lave et GenerelTidsViser interface, som ALLE vores forskellige viser-datatyper skal bruge. På den måde behøver vi kun at forholde os til én datatype når vi bygger vores batteri objekt. Nemlig datatypen GenerelTidsViser. Man kan også sige at man ved at bruge Interfaces kan lade et objekt have mere end én datatype. For når vores SekundViser implementere GenerelTidsViser interfacet, så kan SekundViser pludselig bruges alle de steder hvor datatypen GenerelTidsViser er påkrævet… og hvorfor det? – jo for i og med at SekundViser implementere GenerelTidsViser, så er vi sikret at den har den grænseflade som andre objekter regner med at kunne bruge når de skal tale med den.

På den måde kan man sikre sig at lave en svagere binding klasserne i mellem. To klasser behøver ikke at kende hinanden fra ende til anden, hvis blot den ene implementere et givent Interface, som den anden bruger. På den måde kan man bygge dataobjekter der er langt mere ”frie” til at blive brugt andre steder i andre sammenhænge, når blot en del af deres grænseflade er specificeret i et Interface.

Rent praktisk så er det sådan, at hvis vi laver et Interface i Action Script, og siger at en klasse skal implementere, altså føre dette interface til virkelighed. Så SKAL vores klasse udfører de metoder der står beskrevet i det interface den implementerer. Hvis den ikke gør det, så opdager Flash det selv, og kommer med en fejlmelding. Derfor er det ud over at være en god måde at lave generelle objekt-grænseflader på, også en fantastisk måde at sikre at en klasse har de metoder den skal have.

Eksemplet

Jeg har lavet:

  • et GenerelTidsViser interface
  • en Batteri klasse
  • en SekundViser klasse
  • en MinutViser klasse
  • en TimeViser klasse.

Jeg har kommenteret igennem klasserne og selve funktionaliteten er blot for eksemplets skyld.
Værd at bemærke er at Batteri klassens addViser funktion kan modtage objekter af typen GenerelTidsViser. Og fordi de tre viser klasser alle implementere dette interface opfattes de som om de er sådanne datatyper (hvilket de også er). - og derfor kan Batteri klassen tale til dem via det der er beskrevet i GenerelTidsViser interfacet.

Først interfacet

1
2
3
4
5
6
7
8
9
10
/**
 * @author:   flashforum.dk (flashger) 
 * @usage:    Dette Interface sørger for at alle objekter der implementere dette interface kan bruges de steder hvor datatypen 
 *        GenerelTidsViser er påkrævet.
 */
 
 interface GenerelTidsViser 
 {
   public function updateTid(date:Date):Void;
 }

Her er det så Batteri 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
/**
 * @author: flashforum.dk (Flashger)
 * @desc: Denne klasse holder øje med tiden, og giver besked til sine lyttere af typen GenerelTidsViser når der er noget nyt.
 *
 */
 
 //Vi importere det givne Interface så vi kan bruge denne datatype i Batteri klassen
import GenerelTidsViser;
 
 class Batteri
 {
   //vores holder til generelTidsViser Objekter
   private var generelTidsViserHolder:Array
   
   //vores tidsinterval som er urets motor
   private var tidsInterval:Number;
   
   
   public function Batteri()
   {
     generelTidsViserHolder = new Array();
     setTidsFunktion();
   }
   
   /**
   * Vi sætter vores tidsfunktion op til at kalde "tickTack" funktionen hver 500'ed milisekund
   */
   private function setTidsFunktion():Void
   {
     tidsInterval = setInterval(this,"tickTack",500);
   }
   
   
   /**
   * Vores tickTack funktion løber alle hele listen over generelTidsViserere igennem og kalder deres updateTid funktion.
   */
   private function tickTack():Void
   {
     var now:Date = new Date();
     for(var i:Number = 0; i < generelTidsViserHolder.length; i++)
     {
       generelTidsViserHolder[i].updateTid(now);
     }
   }
   
   
   
   
   
   /**
   * Denne funktion føjer et objekt af typen GenerelTidsViser til Batteriets holder.
   * NOTE: Læg mærke til at vi specificere datatypen som GenerelTidsViser i funktionen. På den måde
   * er det KUN klasser der implementere dette Interface der kan bruges. -> de opfattes, foruden deres egen datatype også
   * som værende GenerelTidsViser datatyper.
   * @param gtv
   */
   public function addViser(gtv:GenerelTidsViser):Void
   {
     //vi føjer vores gtv objekt til holderen
     generelTidsViserHolder.push(gtv);
   }
 }

Så er det SekundViser 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
/**
 *
 *
 */
 
 //vi importere vores interface her, så vi kan bruge det når vi deklarere klassen navn
 import GenerelTidsViser;
 
 //vores klasse implementere genereltidsviser
 class SekundViser implements GenerelTidsViser
 {
   
   //vi gemmer tiden lokalt, så vi kan sammenligne med den nye tid der kommer ind
   private var oldTime:Date;
   
   public function SekundViser()
   {
     oldTime = new Date();
   }
   
   /**
   * Denne funktion SKAL vi definere/implementere ellers får vi ikke lov til at compilere vores SWF fil.
   * Nu er det så op til den enkelte klasse at fortælle hvad der skal ske i updateTid funktionen.
   * 
   * @param d
   */
   public function updateTid(d:Date):Void
   {
     // vi sammenligner de to dates ved at kalde deres getTime, og sammenligner de to, minus hinanden
     //er tallet lig eller større end 1000 (millisekunder) er der gået et sekund.
     if((d.getTime()- oldTime.getTime()) >= 1000)
     {
       trace("Der er gået et sekund");
       oldTime = d;
     }
   }
   
 }

MinutViser 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
/**
 *
 *
 */
 
 //vi importere vores interface her, så vi kan bruge det når vi deklarere klassen navn
 import GenerelTidsViser;
 
 //vores klasse implementere genereltidsviser
 class MinutViser implements GenerelTidsViser
 {
   
   //vi gemmer tiden lokalt, så vi kan sammenligne med den nye tid der kommer ind
   private var oldTime:Date;
   
   public function MinutViser()
   {
     oldTime = new Date();
   }
   
   /**
   * Denne funktion SKAL vi definere/implementere ellers får vi ikke lov til at compilere vores SWF fil.
   * Nu er det så op til den enkelte klasse at fortælle hvad der skal ske i updateTid funktionen.
   * 
   * @param d
   */
   public function updateTid(d:Date):Void
   {
     // vi sammenligner de to dates ved at kalde deres getTime, og sammenligner de to minus hinanden
     //er tallet lig eller større end 5000 (millisekunder)altså 5 sekunder, så "leger" vi der er gået et minut
     if((d.getTime()- oldTime.getTime()) >= 5000)
     {
       trace("Der er gået et minut");
       oldTime = d;
     }
   }
   
 }

Og tilsidst TimeViser 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
/**
 *
 *
 */
 
 //vi importere vores interface her, så vi kan bruge det når vi deklarere klassen navn
 import GenerelTidsViser;
 
 //vores klasse implementere genereltidsviser
 class TimeViser implements GenerelTidsViser
 {
   
   //vi gemmer tiden lokalt, så vi kan sammenligne med den nye tid der kommer ind
   private var oldTime:Date;
   
   public function TimeViser()
   {
     oldTime = new Date();
   }
   
   /**
   * Denne funktion SKAL vi definere/implementere ellers får vi ikke lov til at compilere vores SWF fil.
   * Nu er det så op til den enkelte klasse at fortælle hvad der skal ske i updateTid funktionen.
   * 
   * @param d
   */
   public function updateTid(d:Date):Void
   {
     // vi sammenligner de to dates ved at kalde deres getTime, og sammenligner de to minus hinanden
     //er tallet lig eller større end 1000 (millisekunder)altså 5 sekunder, så "leger" vi der er gået en time
    if((d.getTime()- oldTime.getTime()) >= 10000)
     {
       trace("Der er gået en Time");
       oldTime = d;
     }
   }
   
 }

Og her er koden der skal til i Fla filen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * Da vores klasser ligger i samme mappe som Fla filen, behøver vi
 * ikke importere dem, man kan bruge dem direkte.
 */
 
//Batteri instans
var bat:Batteri = new Batteri();
//Sekund instans
var sek:SekundViser = new SekundViser();
//Minut Instans
var min:MinutViser = new MinutViser();
//Time instans
var tim:TimeViser = new TimeViser();
 
//vi føjer vores visere til batteriet:
bat.addViser(sek);
bat.addViser(min);
bat.addViser(tim);

Jeg skal beklage eventuelle stavefejl eller sprog-spastiske krumspring... dette er mine egne "Rough Cuts" Very Happy Razz