The Object-Oriented Programming Language RubyDe Nederlandse Ruby Homepage




8. Klassen en modules



8.1 Kan een klasse definitie worden herhaald?

Een klasse kan meerdere keren worden gedefinieerd. Iedere definitie wordt bij de vorigen gevoegd. Word een methode geherdefineerd dan zijn de vorigen overridden en niet meer toegankelijk.

8.2 Zijn er klasse veriabelen?

Vanaf Ruby 1.5.3, ja. Een variabele voorafgegeaan door twee at signs (@@) is a klasse variabele die zowel beschikbaar is voor instance en klasse methodes.

   
  class CountEm
@@children = 0
def initialize
@@children += 1
@myNumber = @@children
end
def whoAmI
"I'm child number #@myNumber (out of #@@children)"
end
def CountEm.totalChildren
@@children
end
end

c1 = CountEm.new
c2 = CountEm.new
c3 = CountEm.new
c1.whoAmI # -> "I'm child number 1 (out of 3)"
c3.whoAmI # -> "I'm child number 3 (out of 3)"
CountEm.totalChildren # -> 3

Eerdere versies van Ruby hadden geen klasse variabelen. Echter container klassen (Array, Hash, etc) toegekend aan een klasse constante kunnen gebruikt worden om het zelfde effect te krijgen. Dit voorbeeld gebruikt een array, andere mensen gebruiken liever een hash.

   
class Foo
F = [ 0 ] # pseudo class variable - Array 'F'
def foo
F[0] += 1
puts F[0]
end
end

Dit houd het aantal keer bij dat foo is aangeroepen over alle instanties van de klasse Foo heen.

8.3 Wat is een klasse instance variabele?

   
class Foo
@a = 123 # (1)
def foo
p @a # (2) ... nil not 123
end
end

(1) is een klasse instance variable, en (2) is een gewone instance variable (die, omdat die niet is  geninitialiseerd, de waarde nil heeft). (2) hoort bij een instantie van de klasse Foo, en (1) hoort bij het klasse object Foo, wat een instance is van de klasse Class. (phew!)

Het is niet mogelijk om klasse instantie variabelen te benaderen vanuit instance methodes.

8.4 Wat is een singleton methode?

Een singleton methode is een instance methode die hoort bij een specifiek object.

Je maakt een singleton methode door een object op te nemen in de definitie:

   
class Foo
end

foo = Foo.new
bar = Foo.new
def foo.hello
puts "Hello"
end
foo.hello
bar.hello

Produces:

Hello
prog.rb:10: undefined method `hello' for #<Foo:0x401b49a0> (NameError)

Singleton methodes zijn handig als je een methode wil toevoegen aan een object en het maken van een nieuwe subklasse niet wenselijk is.

8.5 Heeft Ruby klasse methodes?

Een singleton methode van een klasse object is word een klasse methode genoemd. (Eigenlijk word de klasse methode gedefinieerd in de metaklasse, maar dat is voor het grootste deel transparant). Je kan het ook zo zien dat een klasse methode een methode is wiens receiver een klasse is.

Het komt er op neer dat je een klasse methode kan aanroepen zonder dat je instanties van die klasse als receiver nodig hebt.

We maken een singleton methode van de klasse Foo:

   
class Foo
def Foo.test
"this is foo"
end
end

#It is invoked this way.

Foo.test # -> "this is foo"

In dit voorbeeld is Foo.test een klasse methode.

Methodes die zijn gedefinieerd in de klasse Class kunnen door iedere klasse als klasse methode worden gebruikt(!)

8.6 Wat is een singleton klasse?

Een Singleton klasse is een anonieme klasse die is gecreeerd door een klasse te subklassen die hoort bij een bepaald object. Zij vormen een andere manier om de functionaliteit van een bepaald object uit te breiden.

Neem het simple Foo:

   
class Foo        # -> hello<<7>>nil
def hello
print "hello"
end
end

foo = Foo.new
foo.hello

Stel dat we het nodig vinden om klasse functionaliteit toe te voegen voor deze ene instantie:

   
class << foo
attr :name, TRUE
def hello
"hello. I'm " + @name + "\n"
end
end

foo.name = "Tom"
foo.hello # -> "hello. I'm Tom\n"

We hebben nu foo aangepast zonder Foo te veranderen.

8.7 Wat is een module functie?

Een module functie is een private, singleton methode gedefinieerd in een module. Het is in zoverre gelijk aan een klasse methode, dat het kan worden aangeroepen met de theModule.method notatie:

   
Math.sqrt(2)     # -> 1.414213562

Echter , omdat modules als mixin in klasses kunnen worden opgenomen, kunnen module functies ook zonder de prefix worden gebruikt (op die manier worden al de Kernel functies voor objecten beschikbaar gemaakt):

   
include Math
sqrt(2) # -> 1.414213562

Gebruik module_function om van een functie een module functie te maken.

   
module Test
def thing
# ...
end
module_function :thing
end

8.8 Wat is het verschil tussen een klasse en een module?

Modules zijn verzamelingen van methodes en constanten. Ze kunnen geen instanties genereren. Klasses kunnen instanties genereren (objecten) en per instantie een aparte staat vasthouden (instance variabelen).

Modules kunnen als mixin in klasses en andere modules worden opgenomen. De constanten and methodes van de mixin vermengen zich met die van de klasse zelf en breiden op die manier de functionaliteit van de klasse uit. Klasses kunnen niet als mixin gebruikt worden.

Een klasse kan erven van een andere klasse, maar niet van een module.

Een module kan nergens van erven.

8.9 Kan je een modules subklassen?

Nee. Echter, een module kan worden opgenomen in een andere klasse of module om zo multiple inheritance te simuleren (de mixin faciliteit).

Dit genereerd geen subklasse (wat overerving zou vereisen), maar zorgt voor een is_een? relatie tussen de klasse en de module.

8.10 Geef eens een voorbeeld van een mix-in?

De module Comparable verschaft een diverse vergelijkings operators (<, <=, >, between? etc.). Het definieerd deze in termen van aanropen naar de algemene vergelijkings methode, <=>. Echter <=> word er niet in gedefinieerd. 

Stel dat je een klasse wil maken waarin vergelijkingen gebaseesd zijn op het aantal poten van een dier:

   
class MyClass
include Comparable
attr :legs
def initialize(name, legs)
@name, @legs = name, legs
end
def <=>(o)
return @legs <=> o.legs
end
end
c = MyClass.new('cat', 4)
s = MyClass.new('snake', 0)
p = MyClass.new('parrot', 2)

c < s # -> false
s < c # -> true
p >= s # -> true
p.between?(s, c) # -> true
[p, s, c].sort # -> [snake, parrot, cat]

Het enige dat MyClass moet doen is zijn eigen semantiek voor de operator <=> definieeren en de Comparable module als mix-in gebruiken. De methodes van Comparable zijn dan niet meer te onderscheiden van die van MyClass en je klasse heeft ineens meer functionaliteit. Omdat de Comparable module door veel klassen word gebruikt deelt je klasse een consistente en goed begrepen semantiek.

8.11 Waarom zijn er twee manieren om een klasse methode te definieeren?

Je kan een klasse methode definieeren in de klasse definitie en je kan klasse methode definieeren in het top level?

   
class Demo
def Demo.classMethod
end
end

def Demo.anotherClassMethod
end

Er is maar een belangrijk verschil tussen deze twee. In de klasse definitie kan je rechtstreeks aan de klasse constanten refereren omdat deze binnen de scope vallen. In het top level moet je de Class::CONST notatie gebruiken.

8.12 Wat is het verschil tussen load and require?

load laadt een Ruby programma (*.rb) en voert dit uit.

require laadt ook Ruby programma's maar laadt tevens binaire Ruby extension modules (shared libraries of DLLs). Verder zordgt require ervoor dat een functie nooit meer dan een keer wordt geladen.

8.13 Wat is het verschil tussen include and extend?

include gebruikt een module als mix-in voor een klasse of andere module. Methodes van die module worden aangeroepen met de functie-stijl (zonder receiver).

extend word gebruikt om een module op te nemen in een object (instantie). Methodes in de module worden methodes in het object.

8.14 Wat betekent self?

self is de actieve receiver -- het object waarvan de methode word uitgevoerd. Een functie aanroep in functie-stijl impliceerd self als de receiver.

8.15 Waarom kan ik geen variabelen laden vanuit een apart bestand?

Stel dat file1 het volgende bevat:

   
var1 = 99

en dat een ander bestand het laadt:

   
require 'file1'
puts var1

Produces:

prog.rb:2: undefined local variable or method `var1' for #<Object:0x401c1ce0> (NameError)

Je krijgt een foutmelding omdat load en require ervoor zorgen dat locale variabelen worden opgeslagen in een aparte naamloze namespace, waardoor ze in feite worden  weggegooid. Dit is bedoeld om te voorkomen dat je code vervuild word.