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("Bergen!") #=> "Hei Bergen!"
 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.)


Returverdier

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

I tillegg til &-prefikset som brukes for å pakke en block gitt til en metode inn i et Proc-objekt, brukes *-prefikset for å samle flere argumenter i en liste (Array).

  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 fra utsiden til objektet går via metodekall, såkalte get/set metoder.

Instansvariable er "private" på en måte som er mer som protected i andre språk; metoder i sub- og superklasser har tilgang. Men andre instanser har ikke tilgang, selv ikke instanser av samme klasse.

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

Dette tilbyr multippel arv av funksjonalitet.

Klassevariabler

Klassevariabler er variabler som deles mellom alle instanser av klassen, samt instanser av subklasser. (tilsvarende static variabler i Java)

  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, som skjer jevnlig.

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

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

Flere finalizers kan registreres på et objekt.


Next Previous Contents