Introduktion till objektorienterad programmering

I denna artikeln får du lära dig vad objektorienterad programmering är och grunderna i det.

OOP är en förkortning för ObjektOrienterad Programmering, och det är inte ett eget språk, utan snarare ett sätt att tänka på när man strukturerar upp sin kod. Två andra sätt att tänka på är funktionell respektive imperativ programmering. Imperativ programmering är antagligen det du är van vid (variabler, loopar, villkorssatser och det), medan ett program i funktionell programmering enbart består av ett enda stort uttryck som exekveras. Alla språk har inte stöd för OOP, men många har det, och PHP är ett av dem.

I OOP låter man programmeraren skapa egna datatyper, vilka kallas för klasser. Men vad är meningen med att skapa en egen datatyp, undrar du säkert nu? Räcker det inte med de datatyper som redan finns (heltal, flyttal, strängar, boolean, arrayer med flera)? Jo, de räcker gott och väl för att lösa de flesta uppgifter man kan ställas inför, men klasser är ett verktyg som löser dessa uppgifterna på ett bättre och mer strukturerat sätt. Flera små uppgifter kan man utan problem, och antagligen både bättre och snabbare, lösa utan OOP, men när uppgifterna växer blir fördelarna med OOP tydligare och tydligare.

För att visa några av fördelarna i OOP ska vi först lösa ett problem utan att använda OOP, sedan med hjälp av OOP, och slutligen jämföra de för- och nackdelar som finns. Samtidigt som vi löser problemet med hjälp av OOP kommer du få lära dig hur syntaxen för OOP ser ut i PHP. Uppgiften vi ska lösa är att en arbetskamrat (en annan som programmerar) ska kunna hantera ett datum. Detta är vår alltså nya datatyp: ett datum.

Denna uppgiften är redan löst utan OOP i artikeln Exempel på problemlösning, så börja med att läsa den nu, innan du läser vidare i denna artikeln.


Det gick ju bra att lösa uppgiften på det sättet den var löst i den artikeln, kan OOP verkligen lösa det på ett bättre sätt? Svaret är nej, om vi ska lösa den i OOP kommer lösningen att vara exakt likadan, man tänker på samma sätt, men man har en annan struktur på koden.

Vi ska nu visa hur man skapar klassen Datum i OOP istället. Från artikeln vi länkade till ovan vet vi exakt hur lösningsgången ser ut, så vi behöver bara skriva om den till det sättet man skriver den i OOP. Låt oss börja!

Vanligtvis låter man varje klass ligga i en egen fil, så vi börjar med att skapa filen "Datum.php". Det är, logiskt nog, vanligt att klassens namn återfinns i filnamnet.

En ny klass skapas med nyckelordet "class" följt av namnet på klassen samt måsvingar som omsluter allt som har med klassen att göra. För oss blir det alltså såhär:

PHP - Datum.php
<?php class Datum{ // Här lägger vi allt som har med klassen att göra } ?>

En ny instans (ett objekt) av klassen skapas med nyckelordet new följt av klassens namn och parenteser. Även om vår klass inte kan göra något än kan vi fortfarande skapa instanser av den.

PHP - PHP: Hypertext Preprocessor
<?php // Inkludera klassen include_once 'Datum.php'; // Skapa en ny instans av klassen $datum1 = new Datum(); // Skapa ett annat datum $datum2 = new Datum(); ?>

Det är vanligt att man som nybörjare blandar ihop termerna klasser och objekt, så här kommer ett klargörande för vad som är vad:

  • Klasser är mallar för objekten. I det här fallet är klassen Datum.
  • Objekt är instanser av klasser. I det här fallet är variablerna $datum1 och $datum2 objekt och instanser av klassen Datum.

Det första vi bör göra är att ange vilka instansvariabler klassen ska ha. Instansvariabler är variabler som är en del av objektet. Vi vill ha instansvariablerna år, månad och dag, och det anger man genom att skriva såhär:

PHP - Datum.php
<?php class Datum{ // Följande är våra instansvariabler public $år; public $månad; public $dag; } ?>

Nu undrar du kanske varför man har ordet public framför instansvariablerna? Detta är ett av flera olika ord som anger vilken synlighet instansvariabler har, och är en av styrkorna med OOP. Vi ska inte fördjupa oss mer i detta just nu, utan kommer ta itu med det senare i artikelserien, fram tills dess kommer vi låta alla instansvariabler ha synligheten public.

Alla instanser av klassen har nu sina egna instansvariabler, och vi kan tilldela dem och läsa deras värden som om de vore vanliga variabler med hjälp av "->" operatorn på följande sätt:

PHP - PHP: Hypertext Preprocessor
<?php // Inkludera klassen include_once 'Datum.php'; // Skapa en ny instans av klassen $datum1 = new Datum(); // Sätt datumet för datum1 $datum1->år = 2011; $datum1->månad = 4; $datum1->dag = 13; // Skapa ytterligare en ny instans av klassen $datum2 = new Datum(); // Sätt datumet för datum2 $datum2->år = 2008; $datum2->månad = $datum1->månad; // $datum2 får samma månad som $datum1 har $datum2->dag = 15; echo $datum1->år; ?>
2011

Som du ser har vår klass nu mer eller mindre samma funktionalitet som en associativ array. Inte mycket att skryta med, men det ska vi snart ändra på!

Det första vi ska lägga till i klassen är en konstruktor. Det är en metod som anropas direkt när man skapar en ny instans av klassen. En metod är en funktion som är knuten till en klass, och dem skapar man med nyckelordet function. De fungerar precis som vanliga funktioner. En av de få skillnaderna är att konstruktorn inte kan ge något returvärde, och precis som med instansvariabler måste man ange deras synlighet. Vi kommer dock låta alla metoder ha synligheten public tills vi fördjupat oss mer i ämnet (precis som med instansvariablernas synlighet).

Konstruktorn i PHP heter (alltid) "__construct", och den skriver man såhär:

PHP - Datum.php
<?php class Datum{ // Våra instansvariabler public $år; public $månad; public $dag; // Konstruktorn (körs när en ny instans av klassen skapas) public function __construct(){ // Skriv koden här! } } ?>

Om vi vill att alla instansvariabler ska få värdet 0 när man skapar en ny instans av klassen ska vår konstruktor se ut såhär:

PHP - Datum.php
<?php class Datum{ // Våra instansvariabler public $år; public $månad; public $dag; // Konstruktorn (körs när en ny instans av klassen skapas) public function __construct(){ // Ge instansvariablerna värdet 0 $this->år = 0; $this->månad = 0; $this->dag = 0; } } ?>
PHP - PHP: Hypertext Preprocessor
<?php // Inkludera klassen include_once 'Datum.php'; // Skapa en ny instans av klassen $datum = new Datum(); // Skriv ut datumet echo $datum->år.'-'.$datum->månad.'-'.$datum->dag; ?>
0-0-0

Som du ser använder man sig av den speciella variabeln $this i klassen för att peka ut instansvariablerna på raderna 14-16 ovan. Att enbart skriva $år = 0; åstadkommer inte det vi vill, för då kommer PHP att skapa den lokala variabeln $år i metoden och tilldela den värdet 0, och denna variabeln försvinner när metoden har kört färdigt. I till exempel programmeringsspråket Java räcker det med att skriva $år = 0; för att åstadkomma det vi vill, men de som gjort språket PHP har bestämt att man måste ha $this-> framför för att peka ut instansvariabler.

Vi vill ju egentligen inte att alla instansvariablerna ska få värdet 0 när man skapar ett nytt datum, utan när vi skapar en ny instans av klassen vill vi själva kunna ange vilka värden instansvariablerna ska få. När vi skapar en ny instans av en klass kan vi skicka med parametrar till konstruktorn genom att ange dessa inom parenteserna i "new Datum()". Så om vi ändrar vår konstruktor till följande:

PHP - Datum.php
<?php class Datum{ // Våra instansvariabler public $år; public $månad; public $dag; // Konstruktorn (körs när en ny instans av klassen skapas) public function __construct($år, $månad, $dag){ // Ge instansvariablerna värdena som skickades med $this->år = $år; $this->månad = $månad; $this->dag = $dag; } } ?>

Kan vi skriva såhär:

PHP - PHP: Hypertext Preprocessor
<?php // Inkludera klassen include_once 'Datum.php'; // Skapa en ny instans av klassen $datum = new Datum(2011, 4, 13); // Skriv ut datumet echo $datum->år.'-'.$datum->månad.'-'.$datum->dag; ?>
2011-4-13

Vad gör vi härnäst? Jo, nu ska vi skapa alla metoder som ska finnas i klassen. Vi börjar med att lägga till metoden tillSträng.

PHP - Datum.php
<?php class Datum{ // Våra instansvariabler public $år; public $månad; public $dag; // Konstruktorn (körs när en ny instans av klassen skapas) public function __construct($år, $månad, $dag){ $this->år = $år; $this->månad = $månad; $this->dag = $dag; } // Metod för att få datumet som en sträng public function tillSträng(){ // Bygg upp strängen... $sträng = $this->år.'-'; if($this->månad < 10){ $sträng .= '0'; } $sträng .= $this->månad.'-'; if($this->dag < 10){ $sträng .= '0'; } $sträng .= $this->dag; // ...och skicka tillbaka den! return $sträng; } } ?>

Ett objekts metoder anropas på samma sätt som dess instansvariabler och som en vanlig funktion.

PHP - PHP: Hypertext Preprocessor
<?php // Inkludera klassen include_once 'Datum.php'; // Skapa en ny instans av klassen $datum = new Datum(2011, 4, 13); // Skriv ut datumet echo $datum->tillSträng(); ?>
2011-04-13

Såhär ser det ut när vi även lagt in metoden ökaEnDag:

PHP - Datum.php
<?php class Datum{ // Våra instansvariabler public $år; public $månad; public $dag; // Konstruktorn (körs när en ny instans av klassen skapas) public function __construct($år, $månad, $dag){ $this->år = $år; $this->månad = $månad; $this->dag = $dag; } // Metod för att få datumet som en sträng public function tillSträng(){ $sträng = $this->år.'-'; if($this->månad < 10){ $sträng .= '0'; } $sträng .= $this->månad.'-'; if($this->dag < 10){ $sträng .= '0'; } $sträng .= $this->dag; return $sträng; } // Metod som ökar datumet en dag public function ökaEnDag(){ // Antal dagar i denna månaden $dennaMånadensDagar = antalDagarIMånad($this->månad); // Lägger snart till denna funktionen // Kolla om man ska byta månad if($this->dag == $dennaMånadensDagar){ $this->dag = 1; // Kolla om man även ska byta år if($this->månad == 12){ $this->år++; $this->månad = 1; }else{ $this->månad++; } }else{ $this->dag++; } } } ?>

Bara vi lägger till funktionen antalDagarIMånad kommer det fungera, men vart ska vi lägga den funktionen? Den är egentligen inte en del av klassen (den använder sig enbart av parametern $månad, och aldrig av instansvariablerna), så det är en helt vanlig fristående funktion som klassen använder sig av, men som inte är knuten till klassen. Ska man lägga den som en metod i klassen, eller som en funktion utanför? Det är bättre att lägga den som en metod i klassen. Risken för namnkollision på funktioner minskar bland annat då. Olika klasser kan utan problem ha samma namn på sina metoder.

Observera rad 68 nedan. När man anropar en metod inom klassen skriver man $this-> framför, precis som man gör när man vill komma åt instansvariabler.

PHP - Datum.php
<?php class Datum{ // Våra instansvariabler public $år; public $månad; public $dag; // Konstruktorn (körs när en ny instans av klassen skapas) public function __construct($år, $månad, $dag){ $this->år = $år; $this->månad = $månad; $this->dag = $dag; } // Metod för att få datumet som en sträng public function tillSträng(){ $sträng = $this->år.'-'; if($this->månad < 10){ $sträng .= '0'; } $sträng .= $this->månad.'-'; if($this->dag < 10){ $sträng .= '0'; } $sträng .= $this->dag; return $sträng; } // Funktion som returnerar antalet dagar i angiven månad public function antalDagarIMånad($månad){ // Ren data över antalet dagar i varje månad $allaMånadersDagar = array( 1 => 31, // Januari 2 => 28, // Februari (tar ej hänsyn till skottår) 3 => 31, // Mars 4 => 30, // April 5 => 31, // Maj 6 => 30, // Juni 7 => 31, // Juli 8 => 31, // Augusti 9 => 30, // September 10 => 31, // Oktober 11 => 30, // November 12 => 31 // December ); // Returnera antalet dagar i angiven månad return $allaMånadersDagar[$månad]; } // Metod som ökar datumet en dag public function ökaEnDag(){ // Notera, man skriver $this->metodensNamn() för att anropa en metod inom klassen $dennaMånadensDagar = $this->antalDagarIMånad($this->månad); if($this->dag == $dennaMånadensDagar){ $this->dag = 1; if($this->månad == 12){ $this->år++; $this->månad = 1; }else{ $this->månad++; } }else{ $this->dag++; } } } ?>
PHP - PHP: Hypertext Preprocessor
<?php // Inkludera klassen include_once 'Datum.php'; // Skapa en ny instans av klassen $datum = new Datum(2011, 4, 13); // Öka med en dag $datum->ökaEnDag(); // Skriv ut datumet echo $datum->tillSträng(); ?>
2011-04-14

Och så skulle vi minska datumet med en dag.

PHP - Datum.php
<?php class Datum{ // Våra instansvariabler public $år; public $månad; public $dag; // Konstruktorn (körs när en ny instans av klassen skapas) public function __construct($år, $månad, $dag){ $this->år = $år; $this->månad = $månad; $this->dag = $dag; } // Metod för att få datumet som en sträng public function tillSträng(){ $sträng = $this->år.'-'; if($this->månad < 10){ $sträng .= '0'; } $sträng .= $this->månad.'-'; if($this->dag < 10){ $sträng .= '0'; } $sträng .= $this->dag; return $sträng; } // Metod som returnerar antalet dagar i angiven månad public function antalDagarIMånad($månad){ $allaMånadersDagar = array( 1 => 31, 2 => 28, 3 => 31, 4 => 30, 5 => 31, 6 => 30, 7 => 31, 8 => 31, 9 => 30, 10 => 31, 11 => 30, 12 => 31 ); return $allaMånadersDagar[$månad]; } // Metod som ökar datumet en dag public function ökaEnDag(){ $dennaMånadensDagar = $this->antalDagarIMånad($this->månad); if($this->dag == $dennaMånadensDagar){ $this->dag = 1; if($this->månad == 12){ $this->år++; $this->månad = 1; }else{ $this->månad++; } }else{ $this->dag++; } } // Metod som minskar ett datum en dag public function minskaEnDag($datum){ // Kolla om man ska byta månad if($this->dag == 1){ // Kolla om man även ska byta år if($this->månad == 1){ $this->år--; $this->månad = 12; }else{ $this->månad--; } // Antal dagar i denna månaden $dennaMånadensDagar = $this->antalDagarIMånad($this->månad); $this->dag = $dennaMånadensDagar; }else{ $this->dag--; } } } ?>
PHP - PHP: Hypertext Preprocessor
<?php // Inkludera klassen include_once 'Datum.php'; // Skapa en ny instans av klassen $datum = new Datum(2011, 4, 13); // Öka med en dag $datum->minskaEnDag(); // Skriv ut datumet echo $datum->tillSträng(); ?>
2011-04-12

Och slutligen metoden ändra, som ändrar datumet det angivna antalet dagar.

PHP - Datum.php
<?php class Datum{ // Våra instansvariabler public $år; public $månad; public $dag; // Konstruktorn (körs när en ny instans av klassen skapas) public function __construct($år, $månad, $dag){ $this->år = $år; $this->månad = $månad; $this->dag = $dag; } // Metod för att få datumet som en sträng public function tillSträng(){ $sträng = $this->år.'-'; if($this->månad < 10){ $sträng .= '0'; } $sträng .= $this->månad.'-'; if($this->dag < 10){ $sträng .= '0'; } $sträng .= $this->dag; return $sträng; } // Funktion som returnerar antalet dagar i angiven månad public function antalDagarIMånad($månad){ $allaMånadersDagar = array( 1 => 31, 2 => 28, 3 => 31, 4 => 30, 5 => 31, 6 => 30, 7 => 31, 8 => 31, 9 => 30, 10 => 31, 11 => 30, 12 => 31 ); return $allaMånadersDagar[$månad]; } // Metod som ökar datumet en dag public function ökaEnDag(){ $dennaMånadensDagar = $this->antalDagarIMånad($this->månad); if($this->dag == $dennaMånadensDagar){ $this->dag = 1; if($this->månad == 12){ $this->år++; $this->månad = 1; }else{ $this->månad++; } }else{ $this->dag++; } } // Metod som minskar datumet en dag public function minskaEnDag($datum){ if($this->dag == 1){ if($this->månad == 1){ $this->år--; $this->månad = 12; }else{ $this->månad--; } $dennaMånadensDagar = $this->antalDagarIMånad($this->månad); $this->dag = $dennaMånadensDagar; }else{ $this->dag--; } } // Metod som ändrar datumet det angivna antalet dagar public function ändra($antalDagar){ // $ökaEllerMinska är true om vi ska öka, annars false if(0 < $antalDagar){ $ökaEllerMinska = true; }else{ $ökaEllerMinska = false; $antalDagar = -$antalDagar // Gör $antalDagar positiv } // Iterera $antalDagar gånger och utför rätt metod på datumet for($i=1; $i<=$antalDagar; $i++){ if($ökaEllerMinska){ $this->ökaEnDag(); }else{ $this->minskaEnDag(); } } } } ?>
PHP - PHP: Hypertext Preprocessor
<?php // Inkludera klassen include_once 'Datum.php'; // Skapa en ny instans av klassen $datum = new Datum(2011, 4, 13); // Minska datumet 5 dagar $datum->ändra(-5); // Skriv ut datumet echo $datum->tillSträng(); ?>
2011-04-08

Vi har nu skrivit om vår datatyp till OOP struktur, men vad har vi tjänat på det? Inte mycket hittills, för vi har inte använt några av de verktyg OOP har, men vi ska snart titta närmare på några av dessa.

Men vi kan redan nu konstatera några fördelar. När vi inte använde OOP var vi tvungna att skicka med datumet in som en parameter och returnera det nya datumet varje gång vi ville göra något med det. Om vi ville öka ett datum med 5 dagar skrev vi såhär:

PHP - PHP: Hypertext Preprocessor
<?php $datum = ändraDatum($datum, 5); ?>

Medan vi i OOP bara behöver skriva såhär:

PHP - PHP: Hypertext Preprocessor
<?php $datum->ändra(5); ?>

Detta är en fördel vi har. En annan är att namnen på metoderna i en klass är kortare än namnen på funktionerna vi hade innan. Jämför till exempel metoden "ändra" och funktionen "ändraDatum". Bland alla funktionernas namn återfinns ordet Datum, vilket det aldrig gör hos metoderna. Varför? Jo, om vi inte skulle ha det är chansen för namnkollision på funktioner stor. Just nu har vi bara datatypen datum, men om man arbetar med flera olika datatyper är chansen stor att några funktioner hos olika datatyper får samma namn, såvida inte datatypens namn återfinns i funktionens namn. Ta till exempel funktionen "tillSträng", som alla datatyper borde ha (även om det bara är i utveckling- och felsökningssyfte). Detta bekymret har vi inte hos klasser eftersom PHP vet vilken klass varje objekt tillhör och kan utifrån den informationen välja rätt metod, även om olika klasser har samma namn på sina metoder.

Nu är det dags att gå till nästa artikel, där du får lära dig hur man gör vår klass ännu bättre genom att skydda den med synlighet.