Objektorientering i PHP (del 1)

(Denna artikeln har jag tidigare publicerat på phpportalen.net. Tanken är att jag så småningom ska fortsätta att skriva om ämnet).

Inledning

PHP är ett väldigt enkelt programmeringsspråk att lära sig, nästan lite för enkelt att lära sig. Det finns så många sätt att nå ett och samma mål, några är bra och andra mindre bra. Det är också lätt att lära sig grunderna i objektorientering i PHP, men det är en konst att bemästra den fullt ut. När jag började skriva på denna lilla artikelserie/kurs blev jag också varse om att det är väldigt svårt att lära ut. Jag kände mig lika frustrerad som jag gjorde när jag skulle börja lära mig om OOP (Object Oriented Programming)

Sanningen är den att jag har mycket att lära fortfarande. Jag har inte kommit så långt som jag skulle vilja önska. Det är bara att inse, OOP är besvärligt att lära sig för de allra flesta. Jag kommer att nämna en hel del nya och ibland krångliga termer som ni förmodligen inte känner igen från vanlig traditionell PHP-programmering. Men ta er tid att lägga dem på minnet och försök att förstå vad de kan spela för roll för dig och hur du kan nyttja dem.

Denna artikelserie förutsätter att du är väl insatt i grunderna i PHP och funktioner. Du kommer också att behöva PHP5+.

Vad är egentligen objekt?

Objektorienterad programmering utvecklades redan på 60-talet till det norska programspråket Simula 67. Som namnet antyder ett programspråk för simulering, som användes till att simulera avancerade och komplexa fysiska processer. Idén med språket var att man skulle dela upp delar av koden till olika objekt, där alla objekt hade sina egna egenskaper och funktioner. Man skapade en modell av den verklighet man behövde simulera.

Olika objekt skapades som modeller för att efterlikna egenskaperna hos vanliga fysiska objekt – Saker helt enkelt! Dessa objekt kombinerades sedan med varandra i programmen på samma sätt som de flesta saker i verkligheten består av många mindre delar.

Så egentligen skulle frågan i överskriften vara: Vad i ett program behöver modelleras som ett objekt? Men det får nog saken att låta mer komplicerad än vad den egentligen är.

Exempel:

Låt oss säga att vi skapar ett bilspel. Man ska kunna välja mellan olika bilar och även kunna modifiera sin bil genom att byta ut delar som påverkar bilens prestanda. En starkare motor ger en bättre topphastighet, men också en större risk att hjulen spinner loss i starten. En ny hjulupphängning gör att bilen kränger mindre i kurvorna och andra däck ger bättre väggrepp. Alla saker som man kan byta ut påverkar bilens prestanda på något sätt, vingar, kardanaxlar, bromsar m.m.

Alla dessa delar i ett objektorienterat program skulle vara ett objekt. Om vi skulle se på det väldigt enkelt så är alla ”delar” eller alla substantiv i den verkligheten du simulerar objekt i ditt program. Bil, motor, hjulupphängning, däck, vingar, kardanaxlar och bromsar modelleras alla i ditt program till olika objekt.

I spelet ger vi alla delar olika egenskaper som sedan påverkar den samlade egenskapen hos hela bilen: Topphastighet, acceleration, vindmotstånd, väggrepp och bromssträcka t.ex.

Om vi återkopplar tanken på vårt bilspel till språket Simula och varför det utvecklades, så hoppas jag att ni ser kopplingen? Vi måste helt enkelt i vårt spel kunna beräkna/simulera hur bilen ska uppföra sig med givna parametrar. I ett avancerat bilspel skulle vi inte nöja oss med att modellera bilen och alla delarna som objekt. I verkligheten finns det fler ”objekt” som borde påverka bilen i spelet: Vägunderlag, väder och vind m.m.

Objektorienterad programmering visade tydligt sig ha många fördelar när man sysslade med annat än simuleringar också, som exemplet med bilspelet visar.

Fördelarna med objektorientering?

Vad är det som gör objektorientering så intressant? Många skulle säga att OOP ökar din produktivitet, och de är till viss del sant eftersom OOP uppmuntrar till återanvändbar kod. Men det inte alls så besvärligt att skapa vanliga funktioner som du kan återanvända i flera projekt. Om du använder ett av alla ramverk som finns därute så kan du definitivt snabba upp ditt utvecklingsarbete ordentligt. Men frågan är om det är OOP’s förtjänst eller ramverkets.

De allra största fördelarna med OOP är dock möjligheten att skapa en extremt flexibel kod som har ett lättanvänt gränssnitt, minimerar buggar, är enkelt att anpassa, bygga ut och att utöka funktionaliteten på.

Fördelarna är svåra att förklara, men det finns ett gäng verktyg i OOP som tillsammans skapar möjligheten till mycket flexibla lösningar. De är bland annat:

  • Meddelanden (message passing)
  • Arv (inheritance)
  • Polymorfism (polymorphism)
  • Synlighet (visibility)
  • Abstraktion och inkapsling (abstraction, encapsulation)
  • Designmönster (design patterns)

Jag kommer att förklara dem alla närmare nedan.

Grundläggande om objekt

Ett objekt är en enhet, en programdel, som kombinerar och paketerar funktionalitet och data på ett och samma ställe. I sin enklaste form så består ett objekt helt enkelt av ett antal funktioner och variabler. Ett helt objekt kan lätt lagras i en variabel, vilket gör att du kommer åt både funktioner och variabler från ett och samma variabelnamn.

ett objekt

För att skilja fristående funktioner från de som finns i ett objekt så kallar man dem i objektsammanhang för metoder. En metod används ofta för att påverka eller hämta objektets variabler. De döps därför lika ofta till ett verb följt av ett substantiv ex. ”getColour”, eftersom de gör saker med objektet, eller hämtar saker ur det. Alla metoder kan ta ett eller flera argument när den kallas och returnera ett svar tillbaka till den som kallar den, precis som du är van vid ifrån vanliga funktioner.

Variabler kallas i regel för attribut eller egenskaper när de existerar i ett objekt. De kan precis som i vanlig PHP-programmering innehålla strängar, vektorer, siffror, vektorer och resurser t.ex. Som jag nämnde innan så kan även objekt lagras i variabler, så det är fullt möjligt att lagra ett objekt som en egenskap i ett annat objekt.

ett objekt lagrat i ett annat objekt

Du kommer att stöta på många exempel på det längre fram.

Klasser (classes) och objekt (objects)

För att ett objekt ska kunna skapas, så behöver den en mall som definierar hur objektet ska skapas. De mallarna kallas för klasser. En klass kan sägas vara en ritning, ett recept, en mall på hur ett objekt ska fungera. Det är alltså i klassen du definierar alla metoder och egenskaper som du vill att ditt objekt ska ha.

När du skapar ett objekt av en klass så säger man att objektet är en instans av klassen. Du har möjlighet att skapa oändligt många objekt från en och samma klass och alla kommer att se likadana ut och fungera på exakt samma sätt. Jämför det med en fabrik där du tillverkar badankor t.ex. Klassen är gjutformen som bestämmer hur ankorna ska se ut. I fabriken kan du sedan tillverka hur många ankor (objekt) som helst av samma gjutform. Formen slits aldrig ut och nästa anka kommer alltid att lämna maskinen exakt likadan som den förra så länge du använder samma form.

klasser och objekt

När ankorna sedan lämnat gjutformen så kan du välja att måla dem alla i olika färger genom att ge den olika egenskaper. Alla ankor är sina egna objekt med olika egenskaper av samma attribut, men de är i grunden alla tillverkade ur samma form.

olika objekt med olika egenskaper från samma klass

Skapa objekt och använda meddelanden (message passing)

Meddelande i objektorientering är den teknik du använder för att kommunicera med ditt objekt. Meddelanden skickar du till ditt objekt varje gång du anropar en metod i objektet, så i princip så är metoden namnet på ditt meddelande och argumenten är själva meddelandet. Detta borde du känna igen från traditionell PHP-programmering, eftersom funktioner har många likheter med objektmetoder. Men det är bara i OO som det kallas för meddelanden.

Exempel:

Om vi återigen tittar på våra badankor som vi pratade om innan, så behöver vi en klass som är själva gjutformen för våra kommande ankor. Vi kan kalla den klassen kort och gott för ”Duck”. Ankan ska kunna målas och få en unik färg, så en egenskap i klassen måste vara färgen. I klassen ”Duck” lägger vi alltså till en egenskap ”colour”.

Vi vill också ha en metod för att sätta en färg på ankorna som lämnar maskinen. Vi kallar den metoden för ”setColour”. setColour behöver ta ett enda argument – Färgen på vår anka. Vi kan också passa på att införa även ett sätt att hämta färgen på en färdigmålad badanka, ”getColour”. Detta ger oss följande klass:

klassen duck

För att skapa en instans, ett objekt, av en klass i PHP använder man nyckelordet new. Så skapa och lagra tre identiska objekt av samma typ är väldigt enkelt:

$anka1 = new Duck();
$anka2 = new Duck();
$anka3 = new Duck();

Vi har nu tre helt identiska omålade ankor lagrade med varsitt variabelnamn $anka1, $anka2 och $anka 3. Enklare kan det inte vara, vi bara säger till PHP att skapa ett nytt (new) objekt av typen Duck och samtidigt lagrar vi den i en variabel.

För att sätta en färg på våra badankor så måste vi nu skicka ett meddelande till dem, med färger som argument. Vi skickar ett meddelande till var och en av ankorna:

$anka1->setColour(”red”);
$anka2->setColour(”green”);
$anka3->setColour(”blue”);

Om vi har satt upp metoden setColour ordentligt i Duck-klassen, så kommer nu metoden att lagra alla färgerna i egenskapen colour i var och en av badankorna. För att sedan hämta och skriva ut färgerna på ankorna kan vi skicka nya meddelanden till objekten. Vi använder oss av getColour som returnerar färgen:

$anka1->getColour();

En bild och dataflöde över vårt objekt och meddelanden som går till och från objektet ser nu ut så här:

objekt av typen duck

Det kan vara värt att observera att vi måste sätta upp metoderna setColour() och getColour() själva. PHP sätter och hämtar inte egenskapen colour automatiskt bara för att jag döpt metoderna till just set- och getColour. Metoder kan vara just så här enkla, men det är också troligt att du kommer att skapa mer avancerade metoder som behandlar data innan de returnerar ett svar till användaren.

Arv (inheritance)

Ett av objektorienteringens främsta verktyg är arv. Ett objekt kan ärva egenskaper från ett annat objekt och på det viset skapa en ny mer specialiserad variant av objektet den ärver av. Objektet du ärver ifrån kallas för förälder (parent/super class) och den som ärver för barn (child/sub class). Barnet kan komma åt alla metoder och egenskaper som föräldern har, vilket gör att du kan definiera gemensamma metoder och egenskaper i föräldern som sedan alla barn delar.

arv

Flera olika objekt kan ärva från samma förälder, men bara från en (i OOP behövs det inte två kön för att skapa ett barn). En förälder kan däremot ha hur många barn som helst.

I ett barn kan du också välja att ersätta en eller flera av förälderns metoder helt och hållet. Definiera den på nytt med samma namn i barnet och du har ersatt funktionaliteten i föräldern. Det sättet som du skriver över förälderns metoder i barnet kallas för metodöverlagring (method overriding).

Exempel:

Tänk dig en webbshop där olika artiklar till viss del delar egenskaper med varandra. Alla produkter har ett artikelnummer, namn, pris, momssats och vikt t.ex. Men det finns ju andra egenskaper som beror på vilken typ av produkt det rör sig om. En CD har en speltid och artist/grupp och en bok har istället sidnummer och författare. Hur gör du om du vill göra en utskrift där de olika delarna skrivs ut olika (utan att ställa villkor på vilken typ av objekt det är)?

Du kan med OO skapa ett objekt med alla de gemensamma egenskaperna. Sedan låta två specialobjekt ärva de gemensamma metoderna från föräldern där du hanterar olikheterna. Gränssnittet kan vara detsamma i båda barnen, och du använder dem likadant, men du får olika resultat när du till exempel ska göra en snygg utskrift med en beskrivning av produkten.

Polymorfism (polymorphism)

Olika objekttyper, t.ex. olika barn som jag nämnde ovan, kan innehålla samma metodnamn. Varje unikt objekt kan alltså ha olika implementation med samma metod. OOP ger oss med andra ord möjligheten att olika objekt kan ge olika svar på samma metodanrop. Det är det som kallas för polymorfism.

Användaren behöver inte veta hur det aktuella objektet hanterar informationen eller hur metoden fungerar, han behöver bara veta hur han anropar den. Men varje objekt har ett unikt svar på samma metod.

I vanlig programmering har inte den möjligheten. Flera funktioner kan inte ha samma namn utan måste döpas olika. Det gör att du har mindre flexibilitet och gör koden svårare att underhålla.

Exempel:

I din webbshop behöver du ha olika fraktalternativ. Ett alternativ kan vara ett fast pris, ett annat kan beräkna frakten med hjälp av totalvikten på varukorgen. Här kan du välja att kombinera olika fraktobjekt med ditt varukorgsobjekt beroende på vilken typ användaren har valt. De olika fraktalternativen ger olika frakter, de beräknas olika i de olika objekten, men sättet du anropar dem på är desamma.

polymorfism

Varukorgsobjektet behöver alltså inte veta vilket fraktalternativ som har valts, eftersom sättet den använder fraktalternativen på är precis desamma. Det som avgör är vilket fraktobjekt du väljer att kombinera med varukorgen.

Synlighet (visibility)

Även om det är möjligt i PHP så uppmuntras man att inte direkt hämta och ändra ett objekts egenskaper, utan man påverkar istället alltid dem genom objektets metoder. För att användaren inte ska lockas att lagra ett nytt innehåll i en av dina egenskaper utan att gå via metoderna så har erbjuder objektorienteringen ett verktyg som kallas för synlighet.

Publika (public) metoder/egenskaper kan nås i alla sammanhang och är därför helt öppna både inifrån objektet självt och utifrån. Som jag nämnde ovan så sätter man därför sällan egenskaper till publika, medan metoder oftast är det.

Privata (private) metoder/egenskaper kan bara nås inifrån det egna objektet, inte ens barn/subklasser har åtkomst till dessa. Av samma anledning som ovan så sätts alltså oftast egenskaper som privata.

Skyddade (protected) metoder/egenskaper kan nås inifrån objektet och alla barn/subklasser, men inte utifrån objektet.

Detta är viktigt eftersom de bestämmer vilka egenskaper och metoder som användaren ska nå. De skyddar de känsliga interna delarna av objekten och lämnar bara ut information om objektet genom ett öppet och väl definierat gränssnitt av publika metoder. I vanlig PHP har du inte den möjligheten, där är alla funktioner och variabler fritt åtkomliga att påverkas i princip när som helst. Det kan innebära att användaren påverkar en variabel vid fel tillfälle, eller i tron att de gör något annat, med rejäla buggar som följd.

Synligheten är alltså ett säkerhetsverktyg du använder för att styra vilken åtkomst användaren (och andra objekt) ska ha till de metoder och egenskaper som du definierat i ditt objekt.

Abstraktion och inkapsling (abstraktion, encapsulation)

OOP tillåter oss alltså att kombinera funktionalitet och data i ett objekt. Vi väljer att tillhandahålla bara de metoder vi behöver för att styra och kontrollera objekten. Allt som händer inne i objekten, alla komplicerade detaljer, behöver inte användaren bry sig om eftersom koden är inkapslad i objekten genom att dölja både beteende och funktion.

Genom abstraktion reducerar vi alltså alla detaljer till ett enkelt gränssnitt. Ett objekt kan innehålla en rejäl data- och metodstruktur i sitt inre, men användaren ser bara just det som behövs och bakom kulisserna sker magin.

Abstraktion hjälper på detta viset också till att skydda användaren från att behöva repetera samma tråkiga kodstycken gång på gång genom att abstrahera flera rutiner bakom en metod. Ett välutvecklat objektorienterat system låter dig göra komplicerade saker på ett väldigt enkelt sätt, genom att bara skicka meddelanden (via metoder) i olika objekt. Det är det som är abstraktion i ett nötskal.

Designmönster (design patterns)

Designmönster är ingen del av PHP, inga funktioner, utan en teknik och ett hjälpmedel för att förstå hur du bäst kombinerar olika objekt med varandra.

Ett designmönster är en etablerad och väldokumenterad lösning på ett känt problem. Så att säga ett ”best practice” för olika givna problem, färdiga att välja och vraka mellan. Det är ett färdigt koncept för ett framgångsrikt, effektivt och flexibelt kodanvändande. Ett designmönster kan vara litet och enkelt eller stort och avancerat, men gemensamt är att de beskriver hur du använder objekt och hur de interagerar med varandra.

Per definition så ska en beskrivning av ett design mönster ha ett namn så att andra kan referera till den enkelt när du diskuterar med andra eller söker på nätet. Den ska ange ett eller flera liknande problem som exempel på vad du kan använda den. Den ska (givetvis) ha en lösning på problemet, själva mönstret/receptet på lösningen. Den bör också nämna konsekvenserna av att använda det beskrivna mönstret, dess fördelar och eventuella nackdelar.

Exempel:

Håll i hatten, för nu kommer det kanske att bli mycket att ta in. Känner ni att det inte är dags för mer avancerade förklaringar av designmönster i PHP, hoppa då över resten av kapitlet.

Men jag vill ge er en liten aptitretare på vad som komma skall och förhoppningsvis ger det ytterligare en bild av vad designmönster är och förhoppningsvis lite fler ledtrådar om objektorienteringens mysterier. Bilden må se lite avancerad ut, men det ser nog värre ut än vad det är.

Om ni tittar på bilden nedan så beskriver den ett exempel på hur ett antal olika objekt kan relatera till varandra. I detta fall delar av en webbshop (klicka för att se bilden större):

del av objektorienterad webbshop

Det allra enklaste designmönstret måste nog sägas vara ”Dependency Injection”. Några skulle vilja säga att det är så enkelt att det inte ens räknas som ett mönster. Det kan kanske diskuteras, men det är helt klart ett viktigt verktyg i OOP.

Item-objektet är en produkt/artikeln i webbshoppen med tre olika egenskaper; name, price och weight. Dessa Item-objekt ska man kunna placera ett antal av i ShoppingCart-objektet. För det ändamålet så använder vi en metod, addItem(), som tar ett Item-objekt som argument. addItem() tar det objektet du skickar med i meddelandet och lagrar det i en vektor (array) för säkert förvar i ShoppingCart-objektet. Det här sättet att ”skjuta” in ett eller flera nödvändiga objekt in i ett annat är det som kallas för Dependency Injection.

De olika fraktobjekten till höger i bild har vi varit inne och nosat på förut i kapitlet. Via ytterligare en Dependency Injection så kan vi skicka ett meddelande till ShoppingCart-objektet där vi anger vilken typ av fraktberäkning som ska användas. De två fraktberäkningarna ligger i varsitt objekt; FixedShipping och PricedShipping. De har båda en varsin metod, getShipping(), som beräknar fraktkostnaden på olika sätt = Polymorfism, om ni minns. Den ena med fast pris och den andra med ett pris beräknat på totalsumman på de produkter (items) som ligger i varukorgen.

Sättet som vi flyttar ut fraktberäkningarna från ShoppingCart-objektet in till nya olika objekt som lätt är utbytbara, är ett exempel på det mönstret som kallas för ”Strategy Pattern”. Vill vi lägga till ytterligare en fraktberäkning att välja mellan så kan vi göra det genom att skapa ett ”WeightShipping” t.ex. som beräknar frakten utifrån vikten på varorna i varukorgen.

Koden till exemplet som beskrivs här kan du hitta på php-portalen: http://www.phpportalen.net/viewtopic.php?p=675983#675983

Det svåra med designmönster är att veta vilken man ska använda sig av var. Allt eftersom objektorienteringens principer sitter bättre och bättre, så blir designmönster mer och mer intressant att lära sig mer om. En applikation innehåller i regel många mönster som arbetar med varandra i olika kombinationer.

Sammanfattning

Förhoppningsvis har det jag beskrivit gett dig lite insikt om vad objektorientering innebär och vad den kan göra för dig och vad som skiljer den från vanlig traditionell PHP-programmering.

2 svar till “Objektorientering i PHP (del 1)”

  1. Väldigt intressant med OOP och php. Håller på i jobbet att strukturera upp en webbsida. Som har många funktioner så jag ser oop som ett måste för att slippa gå igenom 40 filer för att hitta rätt. Så jag ska lära mej mer om oop och mvc.

    • Ola Waljefors skriver:

      Det är verkligen intressant med OOP och PHP. Det går verkligen att programmera vettiga sidor utan hjälp av OOP, det finns det många bevis för, men jag skulle ha mycket svårt att klara mig utan!

      Under den perioden för några år sedan som jag som bäst höll på att lära mig mer om PHP OO, så märkte jag att även de gånger jag inte använde det så började jag tänka i banor om klasser/objekt. Det är kanske då man nått den gyllene gränsen som betyder att man verkligen börjar förstå att utnyttja fördelarna kanske? Jag märkte att jag närmade mig mer och mer separation av logik och presentation tex, men det blev aldrig riktigt perfekt. Funktioner och variabler räckte inte till och med några få objekt så passade allt som handsken.

      Men det ÄR ofta en tröskel, en tröskel som även jag traskat över (många gånger). Men jag tror att om jag fått rätt information från början så hade det hela gått snabbare.

      Lycka till med din webbsida och tack så mycket för din kommentar!

Lämna ett svar

E-postadressen publiceras inte. Obligatoriska fält är märkta *

Denna webbplats använder Akismet för att minska skräppost. Lär dig hur din kommentardata bearbetas.