Die Programmiersprache Ruby (German Edition)
instance_methods.each do |m|
m = m.to_sym # Ruby 1.8 gibt Strings statt Symbole zurück.
next if m == :object_id || m == :__id__ || m == :__send__
undef_method m
end
# Diese TracedObject-Instanz initialisieren.
def initialize(o, name, stream)
@o = o # Das Objekt, an das wir delegieren
@n = name # Der Objektname für Tracing-Meldungen
@trace = stream # Wohin Tracing-Meldungen gesendet werden
end
# Dies ist die wichtigste Methode von TracedObject. Sie wird
# für so gut wie jeden Methodenaufruf für ein TracedObject
# aufgerufen.
def method_missing(*args, &block)
m = args.shift # Erstes Argument ist der Name der Methode.
begin
# Tracing des Methodenaufrufs
arglist = args.map {|a| a.inspect}.join(', ')
@trace << "Aufruf: #{@n}.#{m}(#{arglist}) in #{caller[0]}\n"
# Methode für Delegate-Objekt aufrufen und Rückgabwert erhalten.
r = @o.send m, *args, &block
# Tracing eines normalen Methoden-Rückgabewerts
@trace << "Rückgabe: #{r.inspect} from #{@n}.#{m} to #{caller[0]}\n"
# Wert des Delegate-Methodenaufrufs zurückgeben.
r
rescue Exception => e
# Tracing einer abnormalen Rückkehr aus der Methode
@trace << "Fehler: #{e.class}:#{e} in #{@n}.#{m}\n"
# Und Ausnahme, die das Delegate-Objekt ausgelöst hat, erneut auslösen.
raise
end
end
# Das Objekt zurückgeben, an das wir delegieren.
def __delegate
@o
end
end
Listing 8.4 Tracing von Methodenaufrufen mit method_missing
8.9.3 Synchronisierte Objekte durch Delegation
In Listing 8.2 haben wir eine globale Methode namens
synchronized
gesehen, die ein Objekt entgegennimmt und einen Block unter dem Schutz des mit diesem Objekt verknüpften
Mutex
ausführt. Der größte Teil des Beispiels bestand aus der Implementierung der Methode
Object.mutex
. Die Methode
synchronized
funktioniert ganz einfach:
def synchronized(o)
o.mutex.synchronize { yield }
end
Listing 8.5 modifiziert diese Methode so, dass sie bei Aufruf ohne einen Block einen
SynchronizedObject
-Wrapper um das Objekt zurückgibt.
SynchronizedObject
ist ein Delegations-Wrapper, der auf
method_missing
basiert. Er ähnelt stark der Klasse
TracedObject
in Listing 8.4 , aber er wurde als Unterklasse der Ruby 1.9-Klasse
BasicObject
geschrieben, so dass kein Bedarf besteht, die Instanzmethoden von
Object
explizit zu löschen. Beachten Sie, dass der Code in diesem Beispiel nicht für sich allein stehen kann; er benötigt die weiter oben definierte Methode
Object.mutex
.
def synchronized(o)
if block_given?
o.mutex.synchronize { yield }
else
SynchronizedObject.new(o)
end
end
# Eine Delegations-Wrapper-Klasse, die method_missing für die
# Thread-Sicherheit verwendet. Anstatt Object zu erweitern und unsere
# Methoden zu löschen, erweitern wir einfach BaseObject, das in Ruby
# 1.9 definiert ist. BaseObject stammt nicht von Object oder Kernel ab,
# so dass die Methoden eines BaseObject keine Top-Level-Methoden
# aufrufen können: Sie sind einfach nicht vorhanden.
class SynchronizedObject < BasicObject
def initialize(o); @delegate = o; end
def __delegate; @delegate; end
def method_missing(*args, &block)
@delegate.mutex.synchronize {
@delegate.send *args, &block
}
end
end
Listing 8.5 Methoden mit method_missing synchronisieren
8.10 Methoden dynamisch erzeugen
Ein wichtiges Verfahren der Metaprogrammierung ist die Verwendung von Methoden, die Methoden erzeugen. Die Methoden
attr_reader
und
attr_accessor
(siehe „7.1.5 Akzessoren und Attribute“ ) sind Beispiele dafür. Diese privaten Instanzmethoden von
Module
werden in Klassendefinitionen wie Schlüsselwörter verwendet. Sie nehmen Attributnamen als Argumente entgegen und erzeugen dynamisch Methoden mit diesen Namen. Die nachfolgenden Beispiele sind Varianten dieser Attributakzessor-Erzeugungsmethoden und demonstrieren zwei verschiedene Möglichkeiten zur Definition von Methoden wie diesen.
8.10.1 Methoden mit class_eval definieren
Listing 8.6 definiert private Instanzmethoden von
Module
namens
readonly
und
readwrite
. Diese Methoden funktionieren wie
attr_reader
und
attr_accessor
und stehen hier, um zu demonstrieren, wie solche Methoden implementiert sind. Die Implementierung ist eigentlich recht einfach:
readonly
und
readwrite
erzeugen zunächst einen String mit Ruby-Code, der die
def
-Anweisungen enthält, die zur Definition entsprechender Akzessormethoden benötigt werden. Danach werten sie diesen Code-String mithilfe von
class_eval
(weiter oben in diesem Kapitel beschrieben) aus. Eine solche Verwendung
Weitere Kostenlose Bücher