Wiki: OO

Startsidan | Senaste ändringarna | Lista alla sidor | Sök


OO

Objektorienterad programering


Bakgrund

Häromdagen fick jag ideen att skriva en mer eller mindre språk-neutral text om obektorienterad programering, som jag (och andra) sedan kan porta till det språk vi skriver en guide för för tillfället.

Viktigt: Härmed bestämmer jag att allt nedan ligger i public domain. Detta eftersom det annars är förbjudet att porta texten (om man inte använder någon licens som komplicerar mer än den förenklar). Om ni lägger till något, ändrar etc Måste ni gå med på detta. Ingen får infoga text härunder som är skyddad av upphovsrätt om de inte har rätt att avsäga sig denna. Uppfattat? Ok, då kör vi.

Inledning

Objektorienterad progammering baseras på klasser. En klass är en slags über-funktioner som gör att du kan bunta ihop olika data - framförallt variabler av olika slag och funktioner - till en datastruktur. Det är egentligen allt - syntaxmässigt - som skiljer objektorienterad kod från annan kodning. Den största skillnaden i praktiken är hur man tänker. För tänket är verkligen anorlunda.

Jag har personligen jämfört objektorienterad programering med derivata - förstår man hur man ska tänka är det busenkelt och superbra, gör man det inte, då blir det knepigt framöver...

Ord och begrepp

  • En klass är - som jag nyss skrev - en struktur som kan innehålla både funktioner och variabler. Vissa icke-objektorienterade språk har liknande - men oftast mer begränsade - strukturer som heter t ex structs eller moduler.
  • En instans av en klass kallas för ett objekt. Det betyder att när du skriver vad som ska finnas i en klass skriver du en klass. När du anropar klassen får du ett objekt av typen klassnamn.
  • En konstruktor är en funktion som automagiskt anropas när ett objekt skapas. Den fixar i allmänhet iordning lite initierings-data, som att sätta lite variabler till lämpliga värden.
  • En destruktor är på liknande sätt en funktion som anropas när ett objekt förstörs. Den frigör i allmänhet allokerade resurser vilket gör att man slipper minnesluckor.
  • Scopes har inte direkt med objektorientering att göra, men är viktiga att förstå. Ett scope betecknas - beroende på programmeringsspråk - ofta med måsvingar, men i framförallt python är det tabbar som visar att ett nytt scope har börjat. En variabel som skapas i en funktion (en funktion omges av måsvingar/tabbar) går bara att komma åt när man är inuti den funktionen. En variabel som skapas i en while-loop dör när man ramlar ut ur loopen. Exempel finns på sidan Scopes. Anledningen till att de är viktiga att förstå är att ni måste förstå vad en klassglobal varabel är, hur den skiljer sig från en global, och från en funktionslokal. Kan ni lista ut det från namnen? Bra Smiley
  • FYLL P...



Tänkande

Tänkandet är inte svårt egentligen - bara anorlunda. I mångt och mycket är det mer logiskt från ett mänskligt perspektiv - hela vår värld är uppbyggd av objekt, och det är också grundpelaren i objektorienterad programering. En människa är ett objekt, och en människa har en massa egenskaper - hon kan gå, äta, tala mm. På samma sätt funkar objektorienterad programmering.

Objekt

I ett program finns det ofta många saker man kan se som objekt. Enligt min erfarenhet är spel det som är tydligast. Objektorientering är dock inte begränsat till spel - tvärt om! Däremot tänker jag använda ett hypotetiskt spel som exempel. I ett spel kanske du har ett objekt spelare, ett objekt fiende, ett objekt sak-man-kan-plocka-upp, osv.

Du bör designa en klass för varje objekt som gör allt som ett objekt av den typen bör kunna göra. Ett objekt av typen fiende bör kunna göra vissa saker, och då lägger du alla de egenskaperna som funktioner (eller metoder - olika språk kallar samma sak olika saker) i klassen fiende. Du kommer åt allt som ligger i en klass på olika sätt i olika språk, men för det mesta är det som gäller objektnamn.varabel, eller objeknamn.funktion()

Säg att allt du vill att din fiende ska kunna göra är att dö, orsaka skada, och ritas ut på skärmen. Då gör du tre funktioner som gör respektive sak. När main-loopen sedan körs i ditt spel så anropar den bara fiende.rita(), och så ritas den ut på skärmen. När den ska orsaka skada anropar du fiende.skada() så gör obektet det. Men hur listar programet ut vem (och om) som ska skadas? Vad sägs om att lägga till en leta-upp-spelare-funktion? Ja, det gör vi! Och så bygger vi ut klassen på det sättet så att objektet får allt fler egenskaper.

Inkapsling

Till slut har du kanske bara två funktioner som resten av programmet ska kunna komma åt: en funktion search-and-destroy, och en funktion för att rita ut sig. Resten skyddar du så att de egenskaperna inte går att komma åt utanför klassen. Det här kallas inkapsling - encapsulation. Den tanken brukar jämföras med en bil - som förare bryr du dig inte om i detalj hur motorn funkar, du vill bara kunna använda ratt och pedaler. Hur resten fungerar är upp till den som skapar bilen. Den funktionen, där fienden rör sig, (ratten) kommer sedan att anropa alla underliggande funktioner som skada(), flytta() och vad du nu vill (motorn).

Här lär vi oss också en annan viktig detalj: man ska kunna byta ut en klass - eller i alla fall all kod i klassen - utan att resten av programmet bryr sig - bara några få funktioner som resten av programmet kommer åt (dessa brukar kallas publika, till skillnad från de hemliga privata) finns och ser likadana ut så ska inte resten av programmet förstå att något hänt.

Rättigheter

Jag har redan berättat att man kan begränsa andras rättigheter att komma åt en funktion eller variabel. Nu ska jag beskriva dem lite...

  • Den mest restriktiva är private. Den går bara att komma åt inifrån den klassen den skapas i.
  • Den som ger lite mer frihet är protected. Den går att komma åt från den klassen den skapas i, samt alla klasser som ärver från den. Gör du en protected variabel/funktion i klassen fiende går den att komma åt i klassen sköldpadda. Gör du en private variabel/funktion så går inte det.
  • Den tredje och sista är public. Den ger alla fullständiga rättigheter att komma åt, och att ändra på den.

Arv

Som en konsekvens av tänket finns det mer än bara ordet klass som skiljer objektorienterad programmering från vanlig. När man tänker objektorienterat finns det helt enkelt flera saker som måste finnas för att det ska gå att göra. En av de sakerna är arv. Det finns antagligen många fiendetyper - sköldpaddor, fiskar, bossar och gud vet vad. Sen kanske det finns flera typer av sköldpaddor - vissa kanske kan flyga, vissa ser ut som skellet osv... Det finns också antagligen flera saker att plocka upp - svampar, blommor, fjädrar osv...

Vi börjar med vår fiende-klass vi designade nyss. En sköldpaddefiende ska se ut som en sköldpadda, men i övrigt ha samma egenskaper som standardfienden. Alltså ärver den alla egenskaper från fiende-klassen, men skriver över utritningsfunktionen (eller vilken funktion det nu är naturligast att göra det i) så att den ritar en sköldpadda istället för något annat. En flygande sköldpadda ska vara som en sköldpadda, men förflytta sig genom luften, så den ärver alla egenskaper en sköldpadda har, men skriver över förflyttningsdelen. På så sätt behöver du bara skriva lite kod, som alla objekt använder, Det gör att koden blir lättare att ändra - inser du att fiendena ger för hög skada, och att spelet blir för svårt, kan du helt enkelt ändra i din basklass för alla fiender, och vips så ändras alla fienders skadenivå.

Polymorphism

Om vi lägger ihop det faktum att programmet inte förstår om vi ändrar lite i de underliggande delarna av fiende-klassen med att en sköldpadda i princip är en helt vanlig fiende fast med annat utseende så kommer vi till slutsatsen att programmet inte vet (eller inte bör veta) om det är en vanlig fiende eller om det är en sköldpadda - eller vilken typ av sköldpadda det är. Den här principen kallas får något så snajsigt superflashigt som Polymorphism. Ordet poly är grekiska för många, och morph är grekiska för form. Polymorphism betyder alltså flera former, och det är just vad din fiende har.

Polymorphism innebär alltså att du skapar ett objekt av typen sköldpadda, och ett av typen fisk. Eftersom båda ärver typen fiende så kan man säga att båda är en fiende - de har bara lite bonus-funktionalitet. Så ditt program kan behandla alla olika fiende-typer som en helt vanlig fiende, utan att någon slutar fungera.

Det klassglobala scopet

Det här är något som varierar lite mellan språken. I allmänhet har man något nyckelord som visar det klassglobala scopet - det här ordet är ofta this. Med andra ord: har du en variabel fart i funktionen, och vill tilldela den klassglobala variabeln fart värdet på den, skriver du i allmänhet som följer:
this.fart == fart;


Implementeringar i olika språk

Här vill jag väldigt gärna ha hjälp, eftersom jag inte kan alla språk. Men jag tänkte i alla fall börja.

Python

Pythons objektorientering är väldig enkel och rättfram. Det har inte så många onödiga ord eller nyckelord - det som behövs räcker. Trots det går allt att göra som går att göra i andra språk.

En klass skapas på följande vis:
  1. class Sköldpadda:

Om klassen ska ärva något annat skriver man såhär:
  1. class Sköldpadda(Fiende):

Python har inga riktiga konstruktorer, men det finns en funktion som alltid körs. Vad som gör att det inte kallas för en riktig konstruktor vet jag inte. Den funktionen är __init__(), alltså med två underscores innan och efter.
  1. class Sköldpadda(Fiende):
  2. def __init__():
  3. pass

Det här kommer dock inte att funka: alla metoder (funktioner som ligger innuti en klass) i python får ett första argument som är en pekare till det klassglobala scopet. Det är en slags de facto-standard att kalla den pekaren för self. Den går att kalla för vadsom, men det är enklare att läsa andras kod om den också använder self.

För att få det att fungera måste man alltså skriva
  1. class Sköldpadda(Fiende):
  2. def __init__(self):
  3. pass

Vill du att en metod (i det här fallet __init__() ) ska kunna ta emot ett argument lägger du helt enkelt det efter self.

Python saknar helt destruktorer. Python anser dessutom att programmeraren ansvarar för att bara anropa de funktioner som programmeraren bör, och har därför inte heller stöd för rättighetsgrejen.

Eftersom Python har löst typade varabler (dvs det är språket, och inte programmeraren, som bestämmer typen på en variabel) så är polymorphism inga problem - det kommer nästan av sig självt: du kan inte skriva kod som bryr sig om vad det är för objekt den anropar.

C++

I C++ skapar man en klass genom att skriva
  1. class Sköldpadda {};

Arv i C++ skrivs som följer:
  1. class Sköldpadda:public Fiende
  2. {};

Det där public som har smugit sig in där betyder att de funktioner i Fiende som är publika också kommer att vara det i Sköldpadda. Det påverkar inte de andra funktionerna. Man kan byta ut public mot protected eller private, och då kommer de publika funktionerna i Fiende att bli det i sköldpadda istället.

(från er också hoppas jag Smiley )




Senast ändrad av ozamosi 11:56 - 25:e September 2005

Atom feed

Innehållet på denna sida kan ändras utav alla som är medlemmar på Blinkenlights, men du måste logga in för att kunna ändra något.