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
Introduktion
I denne artikel vil vi se på 4 kerne/basis forståelser og principper inden for objekt orienteret (OO) udvikling.
Disse begreber/principper er ikke basis viden for at kunne sammenstykke en applikationsarkitektur, men i stedet basis viden for godt design af de enkelte byggesten, nemlig objekterne.
De 4 basis begreber jeg vil gennemgå her er:
- Abstraktion
- Encapulation
- Inheritance
- Polymophisme
Jeg bruger UML til at visuelt at vise nogle forhold i artiklen. Men jeg vil ikke komme ind på notation og meta-modellen brugt i UML.
Abstraktion
Abstraktion bruges om en idé eller en beskrivelse af noget overordnet, hvor denne beskrivelse ikke forholder sig til hvordan denne idé rent faktisk føres ud i livet. Det modsatte af Abstraktion er implementation. Implementation er den faktiske udførsel af det der blev beskrevet abstrakt. Hvis man skal overføre det til et eksempel kan man beskrive en metode/funktion i et programmeringssprog abstrakt ved at sige:
myMethod(name:String,age:int):String
Her er altså kun beskrivelsen af idéen om en funktion ved navn ”myMethod” som tager nogle givne parametre med ind og sender en værdi retur. En sådan beskrivelse af en metode omtales også som metodens signatur. Hvad der sker inde i ”maven” på metoden forholder vi os ikke til, på det abstrakte niveau.
Modsat implementation, hvor vi jo forholder os til hvordan ”myMethod” rent faktisk ser ud inden i. En beskrivelse af ”myMethod” på implementationsniveau kunne se sådan her ud:
myMethod(name:String,age:int):String { var result:String = ”My name is ”+name+” and I’m ”+age+” years old.”; return result;};
Her har vi ikke kun metodens signatur med, men også det man kalder metodens ”body” som den mængde kode der mellem klammerne:
]{ /*method body… */}
Så rule of thumb, i forhold til at snakke om abstrakte metoder, så husk at abstrakte metoder ikke har nogen ”body”.
Man kan også tale om at en hel klasse er abstrakt. Når en klasse er abstrakt betyder det at den aldrig instantiseres. Så lige som med metoden der kun er en signatur, er en abstrakt klasse også kun en signatur på overordnet objektniveau. Et eksempel kunne være at vi har en ”AbstractHuman” klasse, som nogle overordnende metoder og attributter som er gældende for alle personer. Nedarvende fra ”AbstractHuman” har vi, ”Male” og ”Female” klasserne.
Disse klasser er konkrete klasser som begge kan instantiseres, og de rummer begge de fælles egenskaber defineret i ”AbstractHuman” klassen. Men det ville ikke give mening at kunne lave et instans af ”AbstractHuman” klassen, da det i sig selv er for abstrakt til at kunne eksistere. – vi er nød til at have en specialisering af ”AbstractHuman”, før det giver mening at oprette instanser.
En abstrakt klasse kan godt have ikke-abstrakte metoder, men en ikke-abstrakt klasse kan ikke have abstrakte metoder. Forståelsen af hvorfor en ikke-abstrakt klasse, altså en konkret klasse ikke kan have abstrakte metoder ikke så svær at forstå. Men hvorfor en abstrakt klasse kan have ikke-abstrakte metoder, kræver måske lige en kort forklaring. Hvis vi igen tager vores ”AbstractHuman” igen, kunne det være vi havde en abstrakt metode ved navn:
grow(/*…parameters excluded*/):void
-som kun er beskrevet med en signatur. Nu er det hver især ”Male” og ”Female” klassernes ansvar at implementerer denne metode på hver deres måde. Grunden til at metoden er abstrakt er selvfølgelig at mænd og kvinder vokser på hver deres måde, og derfor kan vi ikke definere noget helt fælles for dem begge.
Til gengæld kan vi i ”AbstractHuman” godt definere en konkret metode, som har hele sin implementation, som bliver gældende for både ”Male” og ”Female”. Det kunne være en metode ved navn:
digest():void
-som starter en fordøjelse af mad. Denne process er fælles for både ”Male” og ”Female” og derfor kan og bør den beskrives konkret, i den abstrakte klasse, selvom den abstrakte klasse aldrig selv vil kunne instantiseres.

Et eksempel i UML af forholdet mellem AbstractHuman, Male og Female klasserne.
Indkapsling
Det næste OO basis princip vi kigger på er indkapsling (encapsulation).
Indkapsling er princippet om at beskytte faktisk implementation fra omverdenen. Grunden til at vi gerne vil beskytte faktisk implementation er at det som oftest er det der variere. Hvis objekt1 er afhængig af noget der variere i objekt2, er bindingen mellem de to så kræftig at hvis noget ændres i objek2, skal vi også ændre objekt1.
Det betyder at vi skjuler hvordan udførselen af en metode foregår fra omverden, samt hvad indholdet af forskellige attributter er. For at koble denne tanke sammen med afsnittet omkring abstrakte klasser og metoder, kan man sige at objekt1 kun bør kende det abstrakte ved objekt2, og at den faktiske implementation af objekt2’s metoder og attributter, kun vedrører objekt2. Det vil sige at vi med indkapsling kan få objekter til at forholde sig til hinanden på et mere abstrakt plan, frem for på et konkret plan, hvilket gør objekterne meget lettere at ændre og udskifte uden at det har konsekvens for omkringliggende objekter.
Grunden til at dette er en fordel er at vores kode bliver langt mere fleksibelt for ændringer. – og ændringer er ALTID uundgåelige når man taler om software. – uanset om det er websites, internet applicationer eller large scale enterprise software.
Rent praktisk skjuler man metoder og attributter med såkaldte identifikations nøgleord. Disse nøgleord definere i hvilket omfang en given metode eller attribut skal være tilgængelig for andre. De mest brugte er ”public” og ”private” som nærmest er selvbeskrivende i hvad de gør. ”Public” giver offentlig tilgængelighed for alle andre objekter til at bruge en metode eller en attribut ”Private gør det modsatte og giver kun adgang til metoden eller attributten for det objekt, hvor metoden eller attributten er defineret. Der findes en flere identifikations nøgleord, som er mere specialiserede, men de gennemgås ikke her.
Når man taler om attributter er det normalt at vælge at disse altid er private. Men for at omverdenen skal kunne tilgå disse attributter i et kontrolleret forhold bruger man såkaldte setter/getter metoder. Disse metoder har så ansvar for at sætte eller hente værdier i private attributter. Man kan ved første øjekast så spørge sig selv, hvorfor man overhovedet bør bruge ulejlighed på at gøre attributterne private hvis man alligevel giver adgang til dem med getter/settter metoder.
Til det, er der to gode grunde. Den første er at man før kontrol over hvad kommer ind af værdier. I et eksempel kunne man forestille sig et loadbar objekt med en attribut ved navn ”percentProgress” som kan indeholde heltal. Den kan vi bruge til at indstille hvor meget vores loadbar rent grafisk skal vise der er loaded ind:
loadbar.percentProgress = 90;
Problemet med denne handling er her, at vi jo godt ved at procent ligger fra 0-100, men det gør vores loadbar objekt ikke. Og fordi vi kan stille på percentProgress direkte, har den ikke nogen måde at sørge for at det er valide værdier der kommer ind. vi kunne sagtens putte 200, eller -5 ind i "percentProgress", uden at få en fejl, da både 200 og -5 er et helt tal.
Den anden grund til getter/setter metoderne er tanken om at et ene er ansvarlig for at kende den tilstand (state) det befinder sig i. Hvis vi fortsætter med vores eksempel med loadbar objektet, kan vi sige at bare fordi vi ændre værdien i ”percentProgress”, er det ikke ensbetydende med at loadbar er opmærksom på det. Man kan sige at ansvaret for at bringe loadbar i en tilstand hvor den er loaded 90% ind, pludseligt er flyttet væk fra loadbar objektet selv. Derfor er denne direkte ændring af værdien i ”percentProgress” også en overtrædelse af dette princip om objekters eget state/tilstand.
Derfor kan vi argumentere for denne indkapsling af tingene, og i vores eksempel, tilbage give loadbar objektet ansvar for selv at håndtere sin interne tilstand. Lad os sige at vi i stedet lavede ”percentProgress” privat, og så oprettede en ”public” setter metode ved navn:
setPercentProgress(percent:int):void
Denne metode kan vi så i stedet bruge når vi vil sætte ”percentProgress”. Hvad loadbar gør inde i ”setPercentProgress” er ikke vores ansvar eller interesse.
I eksemplet kunne vores metoden se sådan her ud:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/** * Sets the percent loaded of the loadbar * Must be a integer value within the percentrange 0 - 100 both inclusive * */ public function setPercentProgress(percent:int):void { if(percent > 100) { precent = 100; } else if(percent < 0) { percent = 0; } //we set our internal private percentProgress variable: percentProgress = percent; //Could be two internal methods for running relevant statechanging methods. updataGraphics(); updataTextField(); } |
Her ser vi et eksempel på at vi i vores "setPercentProgress" faktisk ikke bare blindt sætter input værdien ind. Men at vi rent faktisk lige tester værdien af inden. Samtidig bliver der også kaldt nogle interne processer in loadbar, som skal updateres nu hvor der er en ny procentværdi. Så loadbar er slev ansvarlig og bekendt med den tilstand den har.
Lige som abstraktion kan forstås på flere niveauer, kan indkapsling også forstås på flere niveauer.
På objekt plan, med private attributter og metoder, og indkapslede logiske forhold, så som:
- bruger oplysninger.
- et grafisk objekts højde,
- et grafisk objekts længe,
- et grafisk objekts placering,
- et tweens proces
- en timers interne ur.
Eller på component niveua, hvor en gruppe af objekter former et indkapslet component, der har en offentlig grænseflade/API (ikke grafisk grænseflade), men skjuler de varierende værdier internt. Igen kan dette forhold forstørres op til subsystem niveau. Det kunne være et betalings modul, et indkøbs- subsystem, som igen indkapsler det varierende og beskytter det de omkringliggende objekter og systemer.
Nedarving (inherietance)
Vi har allerede set på nedarving, nemlig i eksemplet med abstraktion, hvor ”Male” og ”Female” begge nedarver fra "AbstractHuman".
Nedarving er ideen om at én klasse kan have efterkommere, som arver de metoder og egenskaber som klasse har. Begrebsmæssigt taler man om super og sub klasser, hvor super klassen er den ældste og mest generelle, og subklassen er det nye skud på stammen, som er mere specialiseret end sin forfader. Når et objekt er sub klassen af en super klasse, arver det alt fra super klassen. Men det er ikke alt den har adgang til. Vi talte i afsnittet om indkapsling, om at der kunne defineres forskellige tilgangs identifikationer til metoder og attributter, og her kommer der en ny specialiseret identifikation ind i billedet. Denne identifikation hedder ”protected” og har det formål at holde attributter og metoder skjult fra omverdenen, på nær de objekter der er efterkommere. Det betyder at vi i en klasse kaldet Objekt1 har følgende:
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 |
package { public class Object1 { //private var can only be accesed internally private var message:String = "Hallo World"; //protected, can be accesed by subclasses, besides the class it self protected var description:String = "A hallo world example"; //public everybody is invited to get this public var name:String = "objekt1"; /** * Constructor function. * Models a new instance of Obejct1 * */ public function Object1() { //nothing here; } /** * Tells the world who this obejct is * */ public function tellTheWorld():void { trace(description+": "+message+" called by: "name); } /** * Retrieves the String value in message * */ public function getMessage():String { return message; } } } |
Lader vi Object2 være nedarvet fra Objekt1 kan Objekt2 altså direkte tilgå "description" fordi den er "protected", og "name" fordi "name" er public. Men Object2 kan ikke tilgå "message"
Det betyder ikke at Objekt2 ikke har "message" attributten, den kan blot ikke tilgå den direkte, men må i stedet bruge de samme getter/settter metoder som der er defineret i Objekter1.
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 |
package { public class Object2 { /** * Constructor function. * Models a new instance of Obejct2 * */ public function Object2() { //Call to super class super() //Directy pass at message, null output trace(message); //output: null //Inderectly pass at message trough the inheireted getter method: trace(super.getMessage()); //outputs Hallo World trace(description); //output: "A hallo world example" trace(name); //output object1; } } } |
I eksemplet ovenfor har jeg skrevet <code type="actionscript">super.getMessage(); , for at understrege at kaldet er lavet til de egenskaber der kommer fra super klassen. Det havde været lige så valid blot at skrive getMessage();
Når et objekt er nedarvet fra et andet, har det også tit muligheden for at redefinerer de metoder det har arvet. For igen at vende tilbage til vores eksempel i afsnittet omkring abstraktion, kan man sige at både ”Male” og ”Female” arver metoden ”grow”, men de redefinere den begge på hver deres måde. Denne redefinition kaldes for ”overridding”. I et videre eksempel med klasserne Objekt1 og Objekt2 ser denne proces sådan her 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 |
package { public class Object2 { /** * Constructor function. * Models a new instance of Obejct2 * */ public function Object2() { //Call to super class super() //... trace statements excluded } /** * Override from superclass' tellTheWorld * */ override public function tellTheWorld():void { //We redifine the implementation of tellTheWorld method trace("Another Hallo World example "+super.getMessage()+" called by object2"); } } } |
Hvis en super klasse vil sikre sig at en given metode ikke kan overrides, kan super klassen ud over den almindelig tilgangs identifikation, bruge nøgleordet ”final” som indikere at denne metode er defineret som den er, og altid bør være. I eksemplet med ”AbstractHuman” bør metoden ”digest” sættes til at være final, da ”Male” og ”Female” ikke bør ændre i den.
Polymorphisme
Flerformethed; når et objekt kan være flere former. Polymorphisme har været et af de helt store buzz ord inden for OO de seneste år. Måske fordi man med helt simple midler og principper kan gøre ens kode langt mere fleksibel end tidligere. Skåret til benet handler polymorph om to sider af samme sag: Det at kunne bruge et objekt flere stedet og det at kunne bruge flere forskellige objekter, det samme sted.
I et eksempel kunne man forestille sig at vi havde en ”PatientJournal” klasse, som er en patientjournal som bruges på et sygehus. Patientjournalen er ikke interesseret i om det er en ”Male” eller ”Female” der er tale om. I stedet har ”PatientJournal” klassen defineret at det patienter kun kan være af typen ”AbstractHuman”.
Fordi både ”Male” og ”Female” er nedarvet fra ”AbstractHuman” klassen er de begge en specialiseret form for ”AbstractHuman”, det betyder at de er polymorphe i forhold til at begge at kunne bruges i ”PatientJournal”’s ”setPatient” metode.
Det er en utrolig effektiv måde at gøre sin kode dynamisk og fleksibel på. Vi behøver ikke forholde os til om det er en mand eller en kvinde der kommer ind på vores virtuelle skadestue. Fordi vi ved at det er et menneske, med de generelle egenskaber vi har defineret i "AbstractHuman", kan vi oprette en PatientJournal.
Det eneste vi ikke kan, inde i vores patientjournal, er at bruge metoder eller attributter som er specifikke ved enten ”Male” eller ”Female” klassen. Vi kan kun bruge det generelle for dem.

Denne måde at forholde sig abstrakt til klasser på er et virkeligt vigtigt princip, som bør tilstræbes så meget som muligt.
Hvis vi tager vores ”Male” objekt fra før, så kan vi se at det samme objekt kan bruges i ”BarberShop” klassen som kræver et ”Male” objekt som kunde. Her kan vi ikke bruge AbstractHuman eller Female, da det er nogle helt specifikke metoder, som kun ”Male” har vi gerne vil bruge.
Så med eksemplet ovenfor skulle det gerne være muligt at se at vi kan bruge ”Male” både hvor et ”Male” objekt er krævet, men også hvor et ”AbstractHuman” objekt er krævet. Samtidig kan vi også bruge ”Female” samme sted som ”Male”, i ”PatientJournal” klassen, selvom de to objekter er forskellige fra hinanden, i deres implementation.
Dette er fordi de har et fælles interface eller på dansk, en fælles grænseflade. De har nogle fælles træk, som grænser udadtil, hvordan de så hver især har valgt at implementerer disse fælles træk internt er lige meget, og ikke vigtig i forhold til at de kan bruges samme sted.
Denne tanke om at forsøge at gøre ens kode mere overordnet betegnes ofte med sætningen ”Don’t program to an implementation, but to an interface”. Det betyder at man skal forsøge at vælge så højt niveau af abstraktion, som muligt. Som du har set, giver højere abstraktion mulighed for at flere objekter kan brugs samme sted.
Ofte skabes polymorphisme gennem et såkaldt interface, her skal interfaces forstås som et fysisk ActionsScript dokument. Et interface beskriver en række abstrakte metoder, som de klasser der ønsker at implementerer dette interface skal have.
Du kan læse mere om interfaces i min artikel om emnet ”Interfaces explained”.
Jeg håber du har fået styr på et par af de gennemgående OO begreber, hvis ikke, så hav tålmodighed. Der er meget nyt at forholde sig til, men ved gentagen og regelmæssig brug, kommer det.
Der tages forbehold for stavefejl og kryptiske sprog-formuleringer.
God fornøjelse med din OO udvikling.