Ruby include Part I
September 16th, 2009
Ein häufiger Denkfehler beim Einbinden eines Moduls per include in eine Klasse ist, dass die Methoden aus dem Modul tatsächlich in die Klasse eingefügt werden. Dem ist nicht so. Hier ein Beispiel:
"M1#foo"
end
end
include M1
end
Der Aufruf von include M1 führt intern nicht zu folgendem Code:
"M1#foo"
end
end
Tatsächlich wird durch include eine anonyme Proxyklasse erzeugt, die auf das Modul verweist und die Klasse A erhält eine Referenz auf diese Proxy-Klasse:
-> M1
end
-> proxy
end
a = A.new
a.foo
Im obigen Beispiel findet Ruby die Methode foo also nicht in der Klasse A, sondern (über die Proxy-Klasse) im Modul M1.
Suche entlang der Vererbungshierachie
Grundsätzlich gilt: Um eine Methode auszuführen, muss Ruby die Definition der Methode finden und sucht die Methode dazu entlang der Vererbungshierachie. Ein Beispiel:
include M1
include M2
end
a = A.new
a.foo
Damit ergibt sich diese Suchreihenfolge:
1) A 2) M2 (über proxy) 3) M1 (über proxy) 4) B
Die Vererbungshierachie sieht wie folgt aus:
A -> (proxy->M2) -> (proxy->M1) -> B
Es wird bei der Klasse A begonnen. Wird die Methode nicht gefunden, wird im Modul M2 gesucht. Ist die Methode hier nicht vorhanden, wird im Modul M1 gesucht und existiert die Methode hier nicht, wird in der Superklasse B nachgeschaut. Dort wiederholt sich die Suche.
Daher hat eine in der Klasse A definierte Methode immer Vorrang vor einer gleichnamigen aus einem inkludierten Modul:
"M1#foo"
end
end
-> proxy -> M1
"A#foo"
end
end
a = A.new
a.foo # => "A#foo"
Ruby sucht die Methode entlang der Vererbungshierachie und findet die Methode in der Klasse A bevor es im Modul M1 sucht.
Modulmethode über super aufrufen
Es ist sicher bekannt, dass die Methode super dazu dient, eine gleichnamige Methode in der Superklasse aufzurufen. Beispiel:
"SuperClass#foo"
end
end
"A#foo: "
end
end
a = A.new
a.foo # => "A#foo: SuperClass#foo"
Was vielleicht nicht sofort einleuchtet ist, dass auch die Modulmethode über super aufgerufen werden kann. Das Modul befindet sich (wie die Superklasse auch) in der Vererbungshierachie und daher kann sowohl die Superklassenmethode, als auch die Modulmethode über super aufgerufen werden:
"M1#foo"
end
end
include M1
"A#foo: "
end
end
a = A.new
a.foo # => "A#foo: M1#foo"
Vorrang beim Einbinden von Modulen
Werden zwei Modul eingebunden und erhalten diese dieselbe Methode, so wird die Methode des zuletzt inkludierten Moduls verwendet. Im Beispiel M2:
"M1#foo"
end
end
"M2#foo"
end
end
include M1
include M2
end
a = A.new
a.foo # => M2#foo
Einschränkung der Sichtbarkeit
Mit dem Wissen um die Wirkung von include, wird auch klar, warum die Einschränkung der Sichtbarkeit wie folgt nicht möglich ist:
"mod_method1"
end
end
"mod_method2"
end
end
include M1
private
include M2 # method mod_method2 is still public
end
A.new.mod_method1 # => "mod_method1"
A.new.mod_method2 # => "mod_method2"
Die Methoden aus dem Modul M2 sind öffentlich (public), obwohl das Modul nach dem Schlüsselwort private eingebunden wird. Und zwar, weil die Methode aus dem Modul nicht in die Klasse eingebunden werden, sondern die Klasse auf das Modul verweist (über den Proxy) und mod_method2 in M2 öffentlich ist.
Korrekt geht es wie folgt:
private
"mod_method2"
end
end
include M1
include M2
mod_method2
end
end
A.new.foo # => "mod_method2"
A.new.mod_method1 # => "mod_method1"
A.new.mod_method2 # => NoMethodError: private method ‘mod_method2’ called
for #<A:0x108d8>
Wenn eine Modulmethode nur für interne Implementierung dient, sollte die Methode auf jeden Fall als private deklariert werden.
Proxy-Klasse
Wozu dient eigentlich die Proxy-Klasse? Warum wird nicht direkt eine Referenz auf das Modul in die Klasse eingefügt? Wozu der Umweg über die Proxy-Klasse?
Die Proxy-Klasse ist notwendig, da von Modulen keine Instanzen erzeugt werden können. Dennoch möchte man eventuell Instanzvariablen von Modulen nutzen.
Inkludieren die Klassen A und B das Modul M1, so darf die Änderung einer Instanzvariablen in der Klasse A nicht den Wert in B beeinflussen. Daher gibt für A und B jeweils eine Proxy-Klasse und die die Werte hält.
Alle Proxy-Klasse verweisen auf dasselbe Modul, daher werden Änderung an Methoden des Moduls in alle Klassen sichtbar:
@foo = v
end
"M#foo: "
end
end
include M
self.foo = v
end
end
include M
self.foo = v
end
end
a = A.new(47)
p a.foo # => "M#foo 47"
b = B.new(11)
p b.foo # => "M#foo 11"
"modified M#foo "
end
end
p a.foo # => "modified M#foo 47"
p b.foo # => "modified M#foo 11"
Hoffe, ich habe für mehr Klarheit sorgen können und nicht mehr verwirrt :)
to be continued ...
