9.Lær at programmere

Klasser 

9. Klasser

Indtil nu har vi set mange forskellige slags klasser af objekter : strenge, heltal, floats, sæt og nogle få special objekter(falsk, sand og nil) som vi vil tale om senere. I Ruby  (og på engelsk) skriver vi altid dem med stort: String (streng), Integer (heltal), Float (float), Array (sæt) osv. Normalt bruger vi 'new' hvis vi vil lave et nyt objekt af en bestemat klasse:

a = Array.new + [12345] # Array addition.
b = String.new + 'hello' # String addition.
c = Time.new

puts 'a = '+a.to_s
puts 'b = '+b.to_s
puts 'c = '+c.to_s

a = 12345
b = hello
c = Sun Apr 08 07:29:57 GMT 200


Når vi nu kan lave nye arrays og strenge ved at bruge henholdsvis [...] og '...' bruger vi kun sjældent 'new' (Selvom det ikke er helt indlysende i det ovenståede eksempel, laver String.new en tom streng og Array.new et tomt sæt. ) Ydermere er tal en undtagelse: man kan ikke lave et heltal med Integer.new. Man skriver bare heltallet.

Klassen Time (tid)

Hvordan fungerer klassen 'Time' (tid). Tidsobjekter representerer et øjeblik i tiden. Du kan lægge tal til og trække tal fra time klassen for at få nye tider: hvis du lægger 1,5 til en tid vil du få en tid der er 1,5 sekund senere:

time = Time.new #Det øjeblik du fik denne webpage (på den engelske side)
time2 = time + 60 #Et minut senere.

puts time
puts time2

Sun Apr 08 07:29:57 GMT 2007
Sun Apr 08 07:30:57 GMT 2007


Du kan også lave et tidsobjekt for et bestemt tidspunkt, hvis du bruger 'Time.mktime':

puts Time.mktime(2000, 1, 1) # Y2K.
puts Time.mktime(1976, 8, 3, 10, 11) # Da jeg blev født.

Sat Jan 01 00:00:00 GMT 2000
Tue Aug 03 10:11:00 GMT 1976


Bemærk: dette er da jeg blev født i PDT tidszonen - Stillehavssommertid (Pacific Day Time). Da år 2000 kom, var det Stillehavstid (ikke Stillehavssommertid), i det mindste for os på vestkysten. Parenteserne der der, for at gruppere parametrene til mktime sammen. Jo flere parametre du lægger til jo mere nøjagtig bliver din tid.


Man kan sammenligne tider ved at bruge sammenligningsmetoderne - altså < og > og ==  (et tidligere tidspunkt er mindre end et senere) og hvis du trækker en tid fra en anden vil du få antallet af sekunder mellem dem. Leg lidt med det.



Nogle ting du kan prøve

•En milliard sekunder. Find ud af nøjagtig hvilket sekund du blev født (hvis du kan) Regn ud hvornår du bliver (eller blev) en milliard sekunder gammel. Skriv det i kalenderen !

• Tillykke med fødselsdagen! Spørg hvilket år en person blev født i, så måneden og så dagen. Regn ud hvor gamle de er og giv dem et KLASK vor hver fødselsdag de har haft.

The Hash Class

En anden brugbar klasse er Hash klassen. Hash klassen ligner meget Sæt (arrays) De har en masse felter som kan pege på forskellige objekter. Men felterne er i et Sæt  blot sat pænt op på en talrække  mens de ikke er sat op i nogen rækkefølge i et Hash. De er faktisk rodet sammen. Og du kan bruge ethvert objekt til at pege på et felt, ikke bare et tal. Det er godt at bruge Hash når du har ting du gerne vil holde styr på men som ikke passer ind i en ordnet liste. Et eksempel er farverne som jeg brugte til de enkelte dele af kode som udgør denne lærebog:

colorArray = [] # same as Array.new
colorHash = {} # same as Hash.new

colorArray[0] = 'orange'
colorArray[1] = 'blaa'
colorHash['kodeeksempel'] = 'orange'
colorHash['output'] = 'blaa'

colorArray.each do |farve|
puts farve
end
colorHash.each do |codeType, farve|
puts codeType + ': ' + farve
end



orange
blaa
kodeeksempel: orange
output: bla

Hvis jeg bruger et Sæt (Array) skal jeg huske at felt 0 er til kodeeksempler, felt 1 er til output osv. Men hvis jeg bruger et Hash bliver det let. 'Kodeeksempel' indeholder selvfølgelig farven til kodeeksemplerne, jeg skal ikke huske på noget som helst. Du har måske bemærket at når vi bruger Hash så kommer objekterne ikke nødvendigvis ud i samme rækkefølge som de bliver indsat. Det gjorde de i al fald ikke da jeg skrev dette - måske gjorde de det ligenu - man ved aldrig med Hash objekter). Sæt er til at holde orden på ting - ikke Hash objekter.

Selvom man normalt bruger strenge til at navngive felter i et Hash kan man bruge alle slags objekter, ja selv Sæt og andre Hash objekter - selvom jeg ikke kan finde på nogen som helst grunde til hvorfor du skulle gøre det.

dumtHash = Hash.new

dumtHash[12] = 'aber'
dumtHash[[]] = 'tomhed'
dumtHash[Time.new] = 'Der findes ikke bedre tidspunkt end NU'

Hash og Sæt bruges til forskellige ting - det er op til dig at bestemme hvilken af dem passer bedst til et bestemt problem.


At udvide klasser

I slutningen af sidste kapitel skrev du en metode som skrev det danske tal med bogstaver for en given integer. Det var ikke en integer metode, det var bare en generel 'program' metode. Ville det ikke være dejligt hvis du kunne skrive noget lignende: 22.to_dk i i stedet for 'danskTal 22' :

class Integer
def to_dk
if self == 5
english = 'fem'
else
english = 'fem-oghalvtreds'
end
english
end
end


# Jeg må hellere teste det med nogle tal ...
puts 5.to_dk
puts 58.to_dk

fem

femoghaldvtreds

Ja nu har jeg testet den og det ser ud til at virke ;-)

Så vi har defineret en integer metode ved at hoppe ind i Integer klassen, hvor vi definerer en metode der og hopper ud igen. Nu har alle integer denne (noget ukomplette) metode. Faktisk kan du,  hvis du ikke kan lide den måde en indbygget metode som for eks. to_s virker på, redefinere den på nogenlunde den samme måde. ... men jeg vil ikke anbefale det! Det er bedst at lade være med at lave de gamle om og lave nye når du ønsker at lave noget nyt.

Så... forvirret ? Lad os se lidt mere på det sidste program. Indtil nu, når vi har eksekveret noget kode eller når vi har defineret nogen metoder, så gjorde vi det i 'standart' program objektet. I vores seneste program forlod vi for første gang dette objekt og gik ind i klassen Integer. Vi definerede en metode dér (som altså  gør det til en Integer metode) og alle Integer kan nu bruge den. Inden i metoden brugte vi self til at henvise til objektet (integeren) som brugte metoden.


At skabe klasser

Vi har set et antal forskellige objekt klasser. Det er imidlertid nemt at nævne objekter som Ruby ikke har.. Heldigvis er det lige så nemt at skabe en ny klasse som det er at udvide en eksisterende klasse. Lad os sige at vi ønskede at lave nogle terninger i Ruby. Her er hvordan vi ville lave en Terning klasse.

class Terning
def kast
@talVisning = 1 + rand(6)
end
def visning
@talVisning
end
end
terning = Terning.new

terning.kast
puts terning.visning
puts terning.visning
terning.kast
puts terning.visning
puts terning.visning

3
3
3
3

Smukt! Så kast kaster terningen og visning viser og hvad terningen viser. Men hvad nu hvis vi forsøger at se hvad terningen står på før vi har kastet. 

class Terning
def kast
@talVisning = 1 + rand(6)
end
def visning
@talVisning
end
end
terning = Terning.new
.visning

nil

 

Hmmm... ja, vi fik i det mindste ikke nogen fejlmeddelelse. Men det giver dog ikke meget mening at en terning  skal være ukastet eller hvad 'nil' nu betyder i denne sammenhæng. Det ville være rart hvis vi kunne fastsætte hvad terningen står på når den skabes. Det er hvad 'initialize' bruges til:

 

class Terning

def initialize
     # Jeg vil bare kaste terningen
      # selvom jeg kunne gøre andre ting, foreks bare lade den vise 6
      kast
end

def kast
@talVisning = 1 + rand(6)
end
def visning
@talVisning
end
end
terning = Terning.new
.visning

2

 

Når et objekt skabes kaldes dens initialize metode (hvis den har en) altid.

Vores terning er næsten perfekt nu. Det eneste vi kunne mangle er en metode til at sætte hvilken side af terningen der vises... hvorfor går du ikke bare i gang med at skrive den med det samme. Kom tilbage når du er færdig  (og når du har testet at det virker, selvfølgelig)  Sørg for at man ikke kan sætte terningen til at vise 7!  

Det er ret smarte ting vi lige har lært. Det er dog ikke altid så ligetil, så lad mig give et andet mere interessant eksempel. Lad os sige at vi vil lave et simpelt virtuelt kæledyr, en baby drage. Som de fleste babyer skulle den kunne spise, sove, have afføring, hvilket betyder at vi vil være nødt til at give den mad, lægge den i seng og tage den ud at gå. Internt vil vores drage være nødt til at holde regnskab med om den er sulten, træt eller har behov for en gåtur, men vi kan ikke se disse ting når vi kommunikerer med dyret, præcist ligesom vi ikke kan spørge en menneske baby  om det er sultent. Vi vil også indsætte et par andre sjove måder vi kan kommunikere med vores drage på, og når den fødes vil vi give den et navn (Alt hvad du videregiver til den nye metode, videregives til initialiserings metoden for dig) Lad os fyre løs: 

class Drage
 
  def initialize name
    @name = name
    @sover = false
    @madIMaven     = 10  #  Han er mæt.
    @nogetiEndetarmen =  0  #  Han har ikke behov for at komme ud.
   
    puts @name + ' kommer til verden.'
  end
 
  def giver_mad_til
    puts 'Du giver_mad_til ' + @name + '.'
    @madIMaven = 10
    dergaarnogentid
  end
 
  def gaar_tur_med
    puts 'Du gaar_tur_med ' + @name + '.'
    @nogetiEndetarmen = 0
    dergaarnogentid
  end
 
  def laegger_i_seng
        puts 'Du laegger ' + @name + ' i seng.'
        @sover = true
        3.times do
          if @sover
        dergaarnogentid
          end
          if @sover
        puts @name + ' snorker, og fylder rummet med roeg.'
          end
        end
        if @sover
          @sover = false
          puts @name + ' vaagner langsomt.'
        end
      end
     
      def giver_en_lufttur_til
        puts 'Du giver_en_lufttur_til ' + @name + '.'
        puts 'Han klukker, hvilket faar dine oejenbryn til at blafre.'
        dergaarnogentid
      end
     
      def vugger
        puts 'Du vugger ' + @name + ' blidt.'
        @sover = true
        puts 'He slumrer hen for en stund...'
        dergaarnogentid
        if @sover
          @sover = false
          puts '...men vaagner naar du holder op.'

        end
      end
     
      private
     
      #  "private" betyder at metoderne der defineres her
      #  er metoder interne til objektet. (Du kan give_mad_til
      #  din drage men du kan ikke spørge ham om han er sulten
     
      def hungry?
        #  Metode navne kan ende på "?"
         #  Normalt gør vi kun det hvis metoden returnerer
        #  true eller false ligesom her:
        @madIMaven <= 2
      end
     
      def traengende?
        @nogetiEndetarmen >= 8
      end
     
      def dergaarnogentid
        if @madIMaven > 0
          #  Move food from belly to intestine.
          @madIMaven     = @madIMaven     - 1
          @nogetiEndetarmen = @nogetiEndetarmen + 1
        else  #  Our dragon is starving!
          if @sover
        @sover = false
        puts 'Han vaagner pludseligt!'
          end
          puts @name + ' sulter - i desperation spiste han DIG!'
          exit  #  This quits the program.
        end
       
        if @nogetiEndetarmen >= 10
          @nogetiEndetarmen = 0
          puts 'Whoops!  ' + @name + ' had an accident...'
        end
       
        if hungry?
          if @sover
        @sover = false
        puts 'Han vaagner pludseligt!'
          end
          puts @name + 's mave rumler...'
        end
       
        if traengende?
          if @sover
        @sover = false
        puts 'He wakes up suddenly!'
          end
          puts @name + ' laver sin potte dans...'
        end
      end
     
    end

    pet = Drage.new 'Hansemand'
    pet.giver_mad_til
    pet.giver_en_lufttur_til
pet.gaar_tur_med
pet.laegger_i_seng
pet.vugger
pet.laegger_i_seng
pet.laegger_i_seng
pet.laegger_i_seng
pet.laegger_i_seng 

Hansemand kommer til verden.
Du giver_mad_til Hansemand.
Du giver_en_lufttur_til Hansemand.
Han klukker, hvilket faar dine oejenbryn til at blafre.
Du gaar_tur_med Hansemand.
Du laegger Hansemand i seng.
Hansemand snorker, og fylder rummet med roeg.
Hansemand snorker, og fylder rummet med roeg.
Hansemand snorker, og fylder rummet med roeg.
Hansemand vaagner langsomt.
Du vugger Hansemand blidt.
He slumrer hen for en stund...
...men vaagner naar du holder op.
Du laegger Hansemand i seng.
Han vaagner pludseligt!
Hansemands mave rumler...
Du laegger Hansemand i seng.
Han vaagner pludseligt!
Hansemands mave rumler...
Du laegger Hansemand i seng.
Han vaagner pludseligt!
Hansemands mave rumler...
Hansemand laver sin potte dans...
Du laegger Hansemand i seng.
Han vaagner pludseligt!
Hansemand sulter - i desperation spiste han DIG

Wauu!  Selvfølgelig ville det være smartere hvis det var et interaktivt program men det kan du lave senere. Jeg ville bare prøve at vise de dele der er direkte forbundet med at skave en ny Drage klasse. 

 

Vi så et par nye ting i dette eksempel. For det første så vi at et simpelt 'exit' afslutter programmet lige der hvor det står. For det andet er der ordet 'private' som vi mødte i midten af vores klasse definition.  Jeg kunne have udeladt det men jeg ønskede at gennemtvinge ideen af at der er ting du kan gøre ved dragen medens der er ting der bare sker med dragen. Man kan tænke disse ting som værende 'under motorhjælmen': Med mindre du er auto mekaniker er det eneste du behøver at vide noget om speederen, bremseren og rattet. En programmør ville nok kalde dette  'almindelige funktioner' (public interface) i din bil. Hvordan din airbag   finder ud af at den skal udløses er noget bilen selv skal finde ud af. En normale chauffør skal ikke vide noget om det. 

Faktisk kunne man - hvis jeg skulle give et lidt mere konkret eksempel i stil med ovenstående - representere en bil i et videospil (hvilket faktisk er det jeg arbejder med til daglig). Først ville man bestemme hvordan de 'almindelige funktioner' - public interface - skulle se ud. Med andre ord, hvilke metoder skulle  folk være i stand til at kunne kalde, på vores bilobjekt. Ja, de skal kunne trykke på speederen og på bremsen, men de skulle også være i stand til at specificere hvor hårdt de trykkede på speederen og bremsen.Der er stor forskel mellem lige at berøre den og at trykke sømmet i bund.  De skal også kunne styre og igen skal de kunne fortælle hvor meget de drejer på rattet. Jeg vil tro at man kunne lægge en kobling, blinklys, raket motorstart, efterbrænder og meget mere - det afhænger af hvilken type spil du laver. 

Inde i bilen ville der ske meget mere. En bil ville have en hastighed den vil have en retning og en position som det allermest nødvendige . Disse ting skulle så ændres ved at trykke på speederen og på bremsen og på rettet selvfølgelig.  Men brugeren skal ikke kunne ændre på bilens position direkte.  Man kunne måske også holde styr på udskridning og hvor meget skade bilen har - hvis man har sidevind osv. Disse ting ville alle være indre ting i bilen. 

Nogle ting du kan prøve

• Lav et appelsintræ objekt. Den burde have en højde metode som returnerer dens højde, og en et_år_er_gået metode som når den bliver kaldt, lægger et år til dens alder. Hvert år gror træet højere (så meget som du nu tror at et appelsintræ gror på et år) og efter et antal år, (igen efter dit forgodtbefindende) træet skulle dø.  I træets første leveår skulle træet ikke producere nogen frugt men det skal den efter nogle år, og jeg gætter på at ændre træer producerer flere appelsiner end yngre.  Og selvfølgelig skulle du kunne tælle antallet af appelsiner (en metode der vil fortæller hvor mange appelsiner der er på træet)  og der skal være en tag_en_appelsin metode hvor man kan trække en appelsin fra antallet af appelsiner der er på træet og som siger hvor dejligt den smagte, eller måske fortæller den bare at der ikke er flere appelsiner på træet. Sørg for at appelsiner der ikke er plukket i et år, falder af inden det næste år barsler flere.

•Skriv et program der gør dig i stand til at kommunikere med din baby drage.  Du skal kunne give kommandoer såsom gaa_tur_med og laegger_i_seng og disse metoder skal så kalde dragens metoder.  Du inputter selvfølgelig strenge og derfor må du have en eller anden slags metodesending hvor dit program checker hvilken streng der blev inputtet, hvorefter den passende metode kaldes. 

Og det er så hvad du skal vide om programmering.  Hov, vent lige lidt... jeg har jo ikke fortalt noget om fx at sende en email eller om at gemme eller indlæse en fil  fra din computer eller hvordan man laver 3D verdener ... alt den slags ...! Ja, der er bare så mange klasser som du kan bruge så jeg umuligt kan vise dig dem alle sammen. Jeg kender ikke engang selv de fleste af dem. Men jeg kan fortælle dig  hvor du kan finde ud af mere om dem, så du kan lære dem du ønsker at bruge i din programmering. Før jeg sender dig ud i verden er der dog lige een smart ting mere i Ruby som du bør kende - noget det fleste sprog ikke har men som jeg simpelthen ikke kan leve uden: blokke og processer.

 


Af Chris Pine

oversat af Gunner Carstens

Original tekst

Indledning

Kapitel 1 - Tal

Kapitel 2 -Bogstaver

Kapitel 3 Variable og tildelinger

Kapitel 4 Blande det hele sammen

Kapitel 5 Mere om metoder

Kapitel 6 Kontrol med strømmen

Kapitel 7 Sæt og Gentagere

Kapitel 8 Skriv dine egne metoder

Kapitel 9 Klasser

Kapitel 10 Blokke og Procs

Kapitel 11 Hvad nu?