Next Previous Contents

4. Metoder, klasser og objekter.

Ruby er et særdeles objekt-orientert språk. Metoder, klasser og objekter er de grunnleggende byggesteinene.

4.1 Metoder

Ruby har verken prosedyrer eller funksjoner; kun metoder som kalles på objekter.

  1| # Husker du denne?
  2| def si_hei
  3|   puts "Hei verden!"
  4| end
  5| 
  6| # Hva er vel en funksjon uten argumenter?
  7| def si_hei_til( hva )
  8|   puts "Hei #{hva}"
  9| end
 10| 
 11| si_hei_til("Trondheim!") #=> "Hei Trondheim!"
 12| 
 13| # Funksjoner kan ta flere argumenter og de kan ha default verdier
 14| def send_julegave( til, fra="nissen" )
 15|   puts "God jul, #{til}. Hilsen #{fra}."
 16| end
 17| 
 18| send_julegave("Junior" )    #=> "God jul, Junior. Hilsen nissen."
 19| send_julegave("Ola", "far") #=> "God jul, Ola. Hilsen far."

Du syntes kanskje det så lite objekt-orientert ut? Ikke var metodene definert i noen klasse, og ikke kalte vi dem på noe objekt heller. Det tror du. Alle metoder som defineres på toppnivå defineres i Object-klassen, og vi har implisitt en toppnivå Object-instans. (Prøv self.type og se selv.)

Retur-verdier

Ruby returnerer normalt den siste verdien i metoden, hvis ikke return kalles eksplisitt.

  1| def legg_sammen( a, b)
  2|   a + b   # det siste uttrykket returneres
  3| end
  4| puts legg_sammen( 9, 6 ) #=> 15
  5| 
  6| # fibonacci
  7| def fib( i )
  8|   if i <= 1
  9|     return 1   # vi kan returnere eksplisitt
 10|   end
 11|   return fib( i-1 ) + fib( i-2 )
 12| end
 13| puts fib( 3 ) #=>  3
 14| puts fib( 5 ) #=>  8

Spesielle argumenter

  1| # * prefikset brukes for å pakke argumentlista inn i en Array
  2| def list_opp( og_frase, *args )
  3|   puts args[0..-2].join(", ").capitalize +
  4|     og_frase + args[-1] + '.'
  5| end
  6| 
  7| list_opp( " og ", "epler", "pærer", "bananer" )
  8|   #=> "Epler, pærer og bananer."
  9| 
 10| # eller pakke opp en Array for å bruke elementene som argumenter
 11| a =  [ " and ", "apples", "pears", "bananas" ]
 12| list_opp( *a ) #=> "Apples, pears and bananas."

4.2 Klasser

Som ethvert objekt-orientert språk har Ruby klasser.

  1| # En enkel klasse. 
  2| # Klassenavn må begynne med stor bokstav.
  3| class Person
  4|   # Person.new videresender argumentene til initialize
  5|   def initialize( etternavn, fornavn, alder = 0 )
  6|     # attributter prefikses med @
  7|     @etternavn  = etternavn
  8|     @fornavn    = fornavn
  9|     @alder      = alder
 10|   end
 11|   
 12|   # en vanlig instansmetode
 13|   def to_s
 14|     "#{@fornavn} #{@etternavn} er #{@alder} år."
 15|   end
 16| end
 17| 
 18| if __FILE__ == $0 # Kun når vi kjører denne filen:
 19|   p = Person.new("Nordmann", "Ola", 23) 
 20|   puts p #=> "Ola Nordmann er 23 år."
 21| end

Attributter

Ruby lar deg ikke få tak i et objekts attributter (felter, dataverdier, instansvariable) direkte. Alle attributter er "private". Enhver tilgang går via metodekall, såkalte get/set metoder.

  1| # Vi vil bruke Person-klassen videre
  2| require 'klasse1.rb' 
  3| 
  4| # Klasser er "åpne" skop, og kan enkelt utvides.
  5| class Person  
  6|   # Ruby tillater deg ikke å få tak i attributtene
  7|   # fra utsiden av objektet. Du må gå via metodekall.
  8| 
  9|   #  get-metode
 10|   def alder
 11|     @alder
 12|   end
 13|   # set-metode
 14|   def alder=( ny_alder )
 15|     @alder = ny_alder
 16|   end
 17| 
 18|   # tungvint? Jepp, så Ruby har en snarvei:
 19|   attr_accessor :alder     # definerer metodene over automatisk
 20| 
 21|   # Vi vil også gjerne kunne lese navnene til personen
 22|   attr_reader   :etternavn, :fornavn
 23| 
 24| end
 25| 
 26| if __FILE__ == $0 # Kun når vi kjører denne filen:
 27|   p = Person.new( "Nordmann", "Baby" )
 28|   p.alder = 3     # Vi setter alderen
 29|   puts p.alder    #=> 3
 30|   p.alder += 1    # Øk alderen med et år
 31|   puts p.alder    #=> 4
 32|   puts p.fornavn  #=> "Baby"
 33| end

:alder, :etternavn, :fornavn

Disse er symboler. (instanser av Symbol-klassen) De ligner litt på String, men kan ikke endres, de er "internert" og begrenser seg til lovlige navn på klasser, metoder, variabler o.l. (Symbolet for instans-variabelen @alder, metoden alder og den lokale variabelen alder er alle sammen :alder.)

attr_accessor, attr_reader, attr_writer

Dette er metoder i Module-klassen som lager get/set metoder for deg. Som argument tar de symbolene til attributtene du vil lage get/set metoder for.

Arv

  1| # Fortsetter der vi slapp...
  2| require 'klasse2.rb' 
  3| 
  4| # Arv - alle studenter er en submengde av alle personer
  5| class Student < Person
  6|   def initialize( etternavn, fornavn, alder = 0, 
  7| 		  studiested = "NTNU" )
  8|     # kall super-klassens versjon av metoden
  9|     super( etternavn, fornavn, alder )
 10|     @studiested = studiested
 11|     @karakterer = [] # Eventuelt Array.new
 12|   end
 13|   
 14|   # redefinerer Person#to_s metoden
 15|   def to_s
 16|     "#{@etternavn}, #{@fornavn} - studerer ved #{@studiested}."
 17|   end
 18| 
 19|   def ta_eksamen( karakter )
 20|     @karakterer.push karakter
 21|   end
 22| 
 23|   def karaktersnitt
 24|     sum = 0
 25|     @karakterer.each{ |karakter|
 26|       sum += karakter
 27|     }
 28|     sum.to_f / @karakterer.size
 29|   end
 30|   
 31| end
 32| 
 33| if __FILE__ == $0 # Kun når vi kjører denne filen:
 34|   flinkis = Student.new("Einstein", "Al", 128, "Mensa")
 35|   flinkis.ta_eksamen( 1.0 )
 36|   flinkis.ta_eksamen( 2.0 )
 37|   puts flinkis #=> "Einstein, Al - studerer ved Mensa."
 38|   puts flinkis.karaktersnitt #=> 1.5
 39| end

super

et alias for superklassens versjon av den metoden vi er i nå.

Multippel arv

Beklager. Det er ikke lov å la en klasse arve fra mer enn en superklasse i Ruby.

Derimot har Ruby mixin, som kan legge til funksjonalitet fra flere moduler inn i en klasse. Dvs, du kan bare arve fra en klasse, men kan blande inn funksjonalitet i fra flere moduler.

  1| class Familie
  2|   # Vi inkluderer funksjonalitet fra modulen kalt Enumerable.
  3|   include Enumerable
  4| 
  5|   # Enumerable forventer at each metoden yield'er
  6|   # det som skal itereres over.
  7|   def each
  8|     yield "Far"
  9|     yield "Mor"
 10|     yield "Sønn"
 11|     yield "Datter"
 12|   end
 13| end
 14| 
 15| f = Familie.new
 16| 
 17| # include? og sort metodene er mikset inn fra Enumerable.
 18| puts f.include?("Sønn") #=> true
 19| puts f.sort.join(", ")  #=> "Datter, Far, Mor, Sønn"

I kontrast til Java tilbyr dette multippel arv av funksjonalitet. Java bruker interface til å "etterligne" multippel arv, men tilbyr ikke arv av implementasjon og løser noe som er et ikke-problem når man har dynamisk typing.

Klassevariabler

Klassevariabler (tilsvarende static variable i Java) er variabler som deles mellom alle instanser av klassen, samt instanser av sub-klasser.

  1| class Bil
  2|   # En klassevariabel for å telle antall biler i verden.
  3|   @@num_biler = 0
  4|   def initialize
  5|     @@num_biler += 1
  6|   end
  7|   def Bil.antall
  8|     @@num_biler
  9|   end
 10| end
 11| 
 12| class Lada < Bil
 13| end
 14| 
 15| class Yugo < Bil
 16|   def krasj
 17|     # klassevariabelen er felles for alle instanser av Bil,
 18|     # samt instanser av subklasser av bil
 19|     @@num_biler -= 1
 20|   end
 21| end
 22| 
 23| lada = Lada.new
 24| yugo = Yugo.new
 25| puts Bil.antall #=> 2
 26| 
 27| yugo.krasj
 28| puts Bil.antall #=> 1

Død og begravelse: Finalize

Objekter fødes, brukes og dør. I Ruby dør objektene når de hentes av søppeltømmeren. (garbage collector) Når det skjer, er usikkert. Ingen referanser til objektet må eksistere og garbage collectoren må startes eksplisitt eller implisitt, f.eks. når det begynner å bli lite ledig minne.

  1| streng = "Hvil i fred."
  2| 
  3| # Vi gir en block som skal kjøres når streng objektet dør.
  4| ObjectSpace.define_finalizer(streng){|id|
  5|   puts "Objektet med ID=#{id} er nå dødt. "
  6|   puts "Rest in peace."
  7| }
  8| 
  9| # Starter søppeltømmeren eksplisitt.
 10| puts "Henter søppel!"
 11| GC.start
 12| # Men ingenting skjer, da det ennå er en referanse til strengen.
 13| 
 14| # Prøver en gang til...
 15| streng = nil
 16| puts "Henter mer søppel!"
 17| GC.start
 18| # finalizer blocken blir kjørt.

Legg merke til at objektet allerede er dødt når finalizer block'en kalles. Ressurser som må lukkes eksplisitt, holdes via bindingen til block'en. (Bare pass på at bindingen ikke også holder en referanse til objektet.)

Flere finalizers kan registreres på et objekt.


Next Previous Contents