Die Programmiersprache Ruby

Blog| Forum| Wiki  

Als Vererbung im Sinne der Informatik bezeichnet man den Effekt, dass ein oder mehrere Superklassen (auch Elternklassen) ihre Eigenschaften und/oder Methoden an ihre Subklassen (auch Kindklassen) weitergeben. In Ruby hat jede Klasse - anders als in vielen anderen Programmiersprachen - genau eine direkte[1] Superklasse. Von dieser Superklasse erbt eine Subklasse alle Methoden und benutzt sie, als wären sie ihre eigenen.

Inhaltsverzeichnis

Einfachvererbung

Dies ist reguläre Art der Vererbung und ist recht einfach aufgebaut. Zunächst einmal benötigt man eine Elternklasse, die alle ihre Methoden an eine Kindklasse weitervererben wird. Dann erstellt man diese Kindklasse und gibt bei der Klassendefinition zusätzlich an, von welcher Klasse sie erbt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#Dies ist die Superklasse. 
class Baum
  
  def wasser_aufnehmen
    "Gluck, gluck..."
  end
  
end

#Dies ist die Subklasse - mithilfe des <-Zeichens wird 
#angegeben, von welcher Klasse diese abstammt. 
class Laubbaum < Baum
end

mein_baum = Laubbaum.new
puts mein_baum.wasser_aufnehmen #=> Gluck, gluck..."


Nicht nur Instanzmethoden werden vererbt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Foo
  
  KONSTANTE = 10
  
  @klassen_instanz_variable = 1
  
  @@klassenvariable = -1
  
  def self.klassenmethode
    @@klassenvariable
  end
  
  def self.klassenmethode2
    @klassen_instanz_variable
  end
  
end

class Bar < Foo
end

puts Bar.klassenmethode #=> -1
puts Bar.klassenmethode2 #=> 1
puts Bar::KONSTANTE #=> 10


Ob ein Objekt eine bestimmte Superklasse hat, lässt sich mithilfe von Object#kind_of? prüfen:

1
2
puts Bar.new.kind_of?(Foo) #=> true
puts Baum.new.kind_of?(Foo) #=> false

Um dem Konzept des Duck Typing gerecht zu werden, sollte man diese Methode aber nicht dazu benutzen, sicherzustellen, dass man z.B. einen String erhalten hat. Möchte man wirklich irgendeine Gewissheit haben (was dennoch zumeist unnötig ist), kann man die Methode Object#respond_to? verwenden, die prüft, ob eine bestimmte Methode zur Verfügung steht.

1
2
puts Baum.new.respond_to?(:wasser_aufnehmen) #=> true
puts Foo.new.respond_to?(:wasser_aufnehmen) #=> false


Mehrfachvererbung: Mixins

Wie schon weiter oben beschrieben, nutzt Ruby standardmäßig Einfachvererbung. Das heißt aber nicht, dass Mehrfachvererbung unmöglich wäre; es ist nur so, dass Ruby in dieser Hinsicht ein auf den ersten Blick zwar ungewöhnliches, aber auf den Zweiten nichtsdestotrotz praktisches System nutzt: Mixins.

Ein Mixin ist prinzipiell einfach ein Modul, in dem jedoch keine Modulmethoden definiert wurden, sondern Instanzmethoden. Bestimmt ist jeder von euch schon einmal in diesen Fehler gelaufen:

1
2
3
4
5
6
7
8
9
module Foo
  
  def bar
    "bar"
  end
  
end

puts Foo.bar #=> NoMethodError

Das Problem ist hier offensichtlich, dass bar keine Modulmethode von Foo ist, sondern eine Instanzmethode. Dies führt uns nun zur Mehrfachvererbung: Denn Mixins sind der einzige Weg, um "Instanzmethoden von Modulen" nutzen zu können.

Der Begriff "Mixin" heißt soviel wie "Einmischen" und genau das tut man mit einem Mixin auch: Man mischt es mithilfe der Methode #include in eine Klasse ein.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module MeinMixin

  def instanzmethode
    "Hallo!"
  end
  
end

class MeineSuperklasse
end

class MeineSubklasse < MeineSuperklasse
  include MeinMixin
end

x = MeineSubklasse.new
puts x.instanzmethode #=> Hallo!


Diesen Prozess kann man sooft wiederholen, wie man möchte, es gibt keine Beschränkung für die Anzahl der Mixins.

Bekannte Mixins

Es gibt zwei besonders bekannte Mixins: Enumerable und Comparable. Enumerable wird dazu genutzt, um eine Datenstruktur iterierbar zu machen; es setzt lediglich das Vorhandensein einer each-Methode voraus.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Aufgabenliste
  include Enumerable
  
  def initialize(*aufgaben)
    @aufgaben = aufgaben
  end
  
  def each
    if block_given?
      @aufgaben.each{|aufg| yield(aufg)}
    else
      @aufgaben.enum_for(:each)
    end
  end
  
end

x = Aufgabenliste.new("Kaffee kochen", "den Tisch decken", "den Tisch abr��umen", "Sp��len")
x.each{|aufg| puts aufg}
puts
x.map{|aufg| puts "Ich muss noch #{aufg}!"}
puts
x.find{|aufg| aufg.length < 6}

Ausgabe:

1
2
3
4
5
6
7
8
9
10
11
Kaffee kochen
den Tisch decken
den Tisch abr��umen
Sp��len

Ich muss noch Kaffee kochen!
Ich muss noch den Tisch decken!
Ich muss noch den Tisch abr��umen!
Ich muss noch Sp��len!

Sp��len

Comparable ermöglicht Vergleiche, setzt aber das Vorhandensein der Methode <=> voraus, welche -1 für kleiner, 0 für gleich und +1 für größer zurückgeben muss. Auf dieser Basis definiert es dann die Methoden <, <=, ==, >=, > und between?.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Strich
  include Comparable
  
  attr_reader :laenge
  
  def initialize(laenge)
    @laenge = laenge
  end
  
  def <=>(anderer)
    @laenge <=> anderer.laenge
  end
  
end

x = Strich.new(4)
y = Strich.new(5)
z = Strich.new(4)

puts(x < y) #=> true
puts(x == y) #=> false
puts(x <= z) #=> true
puts(x.between?(Strich.new(1), Strich.new(10)) #=> true


Verwandtschaft

Zusätzlich zu diesen direkten Möglichkeiten der Vererbung gibt es noch eine weitere, die aber im Grunde nur ein Sonderfall der Einfachvererbung ist. In Ruby stammt jedes Objekt, sofern nicht anders angegeben, direkt von der Klasse Object ab (ab 1.9 von BasicObject). Dadurch wird sichergestellt, dass wichtige Methoden wie equal?, das eine Gleichheitsprüfung auf der Objekt-ID durchführt, implementiert werden. Dementsprechend gilt für jedes Objekt:

1
2
3
4
5
6
class Klasse
end

puts Klasse.new.kind_of?(Object) #=> true
#Ab 1.9
puts Klasse.new.kind_of?(BasicObject) #=> true


Fußnoten

[1]Implizit kann eine Klasse natürlich mehrere Superklassen haben, indem sie die Subklasse von X, was die Subklasse von Y, was wiederum die Subklasse von ..., ist.


Siehe auch