|
|||
ObjectSpace::each_object
.
Damit kann man allerlei hübsche Tricks anstellen.
Wenn man zum Beispiel über alle Objekte des Typs Numeric
iterieren möchte, schreibt man
das folgende.
a = 102.7 b = 95.1 ObjectSpace.each_object(Numeric) {|x| p x } |
95.1 102.7 2.718281828 3.141592654 |
Math
-Modul die Definitionen für e und PI; und weil
wir schließlich alle lebenden Objekte unseres Systems sehen wollten, tauchen diese auch auf.
Es gibt da allerdings eine Falle. Wiederholen wir das gleiche Beispiel mit andern Zahlen.
a = 102 b = 95 ObjectSpace.each_object(Numeric) {|x| p x } |
2.718281828 3.141592654 |
Fixnum
-Objekte, die wir erzeugt haben, taucht auf. Das kommt daher,
dass ObjectSpace
keine Objekte mit unmittelbaren Werten kennt:
Fixnum
, true
, false
und nil
.
r = 1..10 # erzeugt ein Bereichs-Objekt |
||
list = r.methods |
||
list.length |
» | 60 |
list[0..3] |
» | ["size", "length", "exclude_end?", "inspect"] |
r.respond_to?("frozen?") |
» | true |
r.respond_to?("hasKey") |
» | false |
"me".respond_to?("==") |
» | true |
num = 1 |
||
num.id |
» | 3 |
num.class |
» | Fixnum |
num.kind_of? Fixnum |
» | true |
num.kind_of? Numeric |
» | true |
num.instance_of? Fixnum |
» | true |
num.instance_of? Numeric |
» | false |
Class#superclass
.
Bei Klassen und Modulen zeigt
Module#ancestors
sowohl Superklassen als auch eingebundene (mixed-in) Module.
klass = Fixnum begin print klass klass = klass.superclass print " < " if klass end while klass puts p Fixnum.ancestors |
Fixnum < Integer < Numeric < Object [Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel] |
ObjectSpace
nutzen, um über alle
Class
-Objekte zu iterieren:
ObjectSpace.each_object(Class) do |aClass| # ... end |
class Demo |
||
private |
||
def privMethod |
||
end |
||
protected |
||
def protMethod |
||
end |
||
public |
||
def pubMethod |
||
end |
||
|
||
def Demo.classMethod |
||
end |
||
|
||
CONST = 1.23 |
||
end |
||
|
||
Demo.private_instance_methods |
» | ["privMethod"] |
Demo.protected_instance_methods |
» | ["protMethod"] |
Demo.public_instance_methods |
» | ["pubMethod"] |
Demo.singleton_methods |
» | ["classMethod"] |
Demo.constants - Demo.superclass.constants |
» | ["CONST"] |
Module.constants
gibt
alle über dieses Modul erhältliche Konstanten zurück, auch die aus den Super-Klassen. An denen sind
wir im Moment nicht interessiert, also entfernen wir sei aus unserer Liste.
Hat man jetzt eine solche Liste mit Methoden-Namen, möchte man sie auch aufrufen. In Ruby ist das
glücklicherweise ganz einfach.
typedef struct { char *name; void (*fptr)(); } Tuple; Tuple list[]= { { "play", fptr_play }, { "stop", fptr_stop }, { "record", fptr_record }, { 0, 0 }, }; ... void dispatch(char *cmd) { int i = 0; for (; list[i].name; i++) { if (strncmp(list[i].name,cmd,strlen(cmd)) == 0) { list[i].fptr(); return; } } /* not found */ } |
commands
)und bitte das Objekt um Ausführung der Methode mit demselben Namen wie
der Kommando-String.
commands.send(commandString) |
send
schrieben: das
funktioniert mit jedem Objekt.
"John Coltrane".send(:length) |
» | 13 |
"Miles Davis".send("sub", /iles/, '.') |
» | "M. Davis" |
Method
-Objekte.
Ein Method
-Objekt ist ähnlich wie ein Proc
-Objekt: Es
repräsentiert ein Stückchen Code und einen Kontext, in dem er ausgeführt wird. In diesem Fall ist der
Code der Rumpf der Methode und der Kontext ist das Objekt, das die Methode erzeugt hat. Wenn man so ein
Method
-Objekt erst mal hat, kann man es später mit der Meldung call
ausführen.
trane = "John Coltrane".method(:length) |
||
miles = "Miles Davis".method("sub") |
||
|
||
trane.call |
» | 13 |
miles.call(/iles/, '.') |
» | "M. Davis" |
Method
-Objekt nun in der Gegend herumschicken wie jedes andere
Objekt auch, und wenn man
Method#call
aufruft,
wird die Methode ausgeführt, als hätte man sie mit dem ursprünglichen Objekt aufgerufen. Es ist, als hätte
man wie in C einen Function-Pointer, aber jetzt in völlig objekt-orientierter Weise.
Man kann Method
-Objekte auch mit Iteratoren benutzen.
def double(a) |
||
2*a |
||
end |
||
|
||
mObj = method(:double) |
||
|
||
[ 1, 3, 5, 7 ].collect(&mObj) |
» | [2, 6, 10, 14] |
eval
-Methode (und ihre Variationen wie
class_eval
, module_eval
und instance_eval
) liest einen
willkürlichen String mit legalem Ruby-Quell-Code und führt ihn aus.
trane = %q{"John Coltrane".length} |
||
miles = %q{"Miles Davis".sub(/iles/, '.')} |
||
|
||
eval trane |
» | 13 |
eval miles |
» | "M. Davis" |
eval
benutzt, kann es hilfreich sein, explizit den Kontext anzugeben, in
dem der Ausdruck ausgewertet werden soll, statt einfach nur den aktuellen Kontext zu nehmen.
Man kann einen solchen Kontext erhalten, indem man
Kernel#binding
an der gewünschten Stelle aufruft.
class CoinSlot def initialize(amt=Cents.new(25)) @amt = amt $here = binding end end a = CoinSlot.new eval "puts @amt", $here eval "puts @amt" |
$0.25USD nil |
eval
wertet @amt
aus im Kontext der Instanz der Klasse
CoinSlot
. Das zweite eval
wertet @amt
aus im Kontext von
Object
, wobei die Instanz-Variable @amt
nicht definiert ist.
Object#send
,
Method#call
und die verschiedenen Varianten von eval
.
Man kann jetzt eine dieser Methoden je nach Bedarf bevorzugen, aber man sollte sich darüber im
Klaren sein, dass eval
deutlich langsamer als die anderen ist (oder, für die Optimisten unter uns,
send
und call
sind deutlich schneller als eval
).
require "benchmark" # from the Ruby Application Archive include Benchmark test = "Stormy Weather" m = test.method(:length) n = 100000 bm(12) {|x| x.report("call") { n.times { m.call } } x.report("send") { n.times { test.send(:length) } } x.report("eval") { n.times { eval "test.length" } } } |
user system total real call 0.270000 0.000000 0.270000 ( 0.243899) send 0.250000 0.000000 0.250000 ( 0.227987) eval 2.990000 0.050000 3.040000 ( 2.929909) |
Kernel::system
um
[Diese von Eiffel inspirierte Fähigkeit, bei der man eine Eigenschaft umbenennt und neu
definiert, ist sehr nützlich, aber man sollte auf die Gefahren achten. Wenn eine Unterklasse das selbe macht
und die Methoden wieder mit den selben Namen umbenennt, erhält man eine Endlos-Schleife. Das kann man vermeiden
mit einem Alias der Methode auf einen eindeutigen Namen oder durch die Benutzung einer konsistenten
Namens-Konvention.]
und ersetzt sie durch eine eigene, die sowohl das Kommando protokolliert als auch die
ursprüngliche Kernel
-Methode aufruft.
module Kernel alias_method :old_system, :system def system(*args) result = old_system(*args) puts "system(#{args.join(', ')}) returned #{result}" result end end system("date") system("kangaroo", "-hop 10", "skippy") |
Sun Mar 4 23:25:46 CST 2001 system(date) returned true system(kangaroo, -hop 10, skippy) returned false |
Class#new
,
der Methode, die Speicherplatz für ein neues Objekt anfordert. Die Technik ist nicht perfekt ---
einige eingbaute Klassen wie literale Strings werden ohne new
konstruiert --- aber bei
selbst geschriebenen Objekten klappt das gut.
class Class alias_method :old_new, :new def new(*args) result = old_new(*args) result.timestamp = Time.now return result end end |
Object
selber.
class Object def timestamp return @timestamp end def timestamp=(aTime) @timestamp = aTime end end |
class Test |
||
end |
||
|
||
obj1 = Test.new |
||
sleep 2 |
||
obj2 = Test.new |
||
|
||
obj1.timestamp |
» | Sun Mar 04 23:25:46 CST 2001 |
obj2.timestamp |
» | Sun Mar 04 23:25:48 CST 2001 |
Ereignis | Callback-Methode | |||||||
Hinzufügen einer Instanz-Methode | Module#method_added |
|||||||
Hinzufügen einer Singleton-Methode | Kernel::singleton_method_added |
|||||||
Erzeugen einer Unter-Klasse | Class#inherited |
|||||||
Einbinden eines Moduls | Module#extend_object |
|||||||
set_trace_func
führt ein Proc
aus und liefert alle möglichen pikanten Debug-Informationen für
jede ausgeführte Quell-Zeile, jede aufgerufene Methode, jedes erzeugte Objekt und so weiter.
Auf Seite 426 gibt es die komplette Beschreibung, aber hier ist schon mal ein Appetithappen.
class Test def test a = 1 b = 2 end end set_trace_func proc { |event, file, line, id, binding, classname| printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname } t = Test.new t.test |
line prog.rb:11 false c-call prog.rb:11 new Class c-call prog.rb:11 initialize Object c-return prog.rb:11 initialize Object c-return prog.rb:11 new Class line prog.rb:12 false call prog.rb:2 test Test line prog.rb:3 test Test line prog.rb:4 test Test return prog.rb:4 test Test |
trace_var
(Beschreibung auf Seite 431), mit der man
einen Hook an eine globale Variable hängen kann; bei jeder Zuweisung an diese globale Variable
wird dann das angegebene Proc
-Objekt aufgerufen.
caller
, die ein Array
aus
String
-Objekten zurückliefert mit dem Inhalt des aktuellen
Aufruf-Stacks.
def catA puts caller.join("\n") end def catB catA end def catC catB end catC |
prog.rb:5:in `catB' prog.rb:8:in `catC' prog.rb:10 |
Marshal::dump
.
Üblicherweise deponiert man so einen kompletten Objektbaum, angefangen bei irgendeinem angegebenen Objekt.
später kann man dieses Objekt mit Marshal::load
wieder rekonstruieren.
Hier kommt ein kurzes Beispiel. Wir haben eine Klasse Chord
, die eine Liste mit
Musiknoten enthält. Wir möchten einen besonders schönen Akkord (Chord) abspeichern, damit unsere Enkel sich auch
noch in Ruby (Version 23.5) daran erfreuen können. Fangen wir mit den Klassen für die Noten
Note
und den Akkord Chord
an.
class Note attr :value def initialize(val) @value = val end def to_s @value.to_s end end class Chord def initialize(arr) @arr = arr end def play @arr.join('-') end end |
Marshal::dump
als
serialisierte Version auf Diskette.
c = Chord.new( [ Note.new("G"), Note.new("Bb"), Note.new("Db"), Note.new("E") ] ) File.open("posterity", "w+") do |f| Marshal.dump(c, f) end |
File.open("posterity") do |f| |
||
chord = Marshal.load(f) |
||
end |
||
|
||
chord.play |
» | "G-Bb-Db-E" |
IO
können nicht außerhalb der laufenden Ruby-Umgebung gesichert werden (man bekommt
einen TypeError
, wenn mans trotzdem versucht). Auch wenn das abzuspeichernde
Objekt keine dieser problematischen Objekte enthält, möchte man vielleicht doch selber die
Kontrolle über die Serialisation behalten.
Marshal
liefert dazu die benötigten Hooks. Dazu implementiert man in den
Objekten, die eine maßgeschneiderte Serialisierung benötigen, einfach zwei Methoden:
eine Instanz-Methode mit Namen _dump
, die das Objekt in einen String schreibt,
sowie eine Methode mit Namen _load
, die aus dem String wieder in ein neues
Objekt macht.
Die folgende Beispiel-Klasse definiert ihre eigene Serialisierung. Aus unerfindlichen Gründen möchte
Special
nicht, dass die internen Daten aus ``@volatile
'' abgespeichert
werden.
class Special def initialize(valuable) @valuable = valuable @volatile = "Goodbye" end def _dump(depth) @valuable.to_str end def Special._load(str) result = Special.new(str); end def to_s "[email protected]} and [email protected]}" end end a = Special.new("Hello, World") data = Marshal.dump(a) obj = Marshal.load(data) puts obj |
Hello, World and Goodbye |
Marshal
ab
Seite 432.
require 'drb' class TestServer def doit "Hello, Distributed World" end end aServerObject = TestServer.new DRb.start_service('druby://localhost:9000', aServerObject) DRb.thread.join # Don't exit just yet! |
require 'drb' DRb.start_service() obj = DRbObject.new(nil, 'druby://localhost:9000') # Now use obj p obj.doit |
doit
auf, die
einen String zurückliefert, den der Client dann ausgibt:
"Hello, Distributed World" |
nil
für DRbObject
bedeutet, dass
wir ein neues verteiltes Objekt anhängen wollen. Wir könnten auch schon existierende
Objekte nehmen.
Je nun, hören wir den Leser sagen. Das hört sich an wie RMI von Java oder wie
CORBA oder sowas. Ja, es ist ein Mechanismus für funktionale verteilte Objekte --- aber es ist mit
nur 200 Zeilen Ruby-Code geschrieben. Kein C, nichts Extravagantes, nur schlichter alter Ruby-Code.
Natürlich gibt es dafür dann keinen Name-Service, keinen Trader-Service oder was es sonst so gibt in CORBA,
aber es ist einfach und recht schnell. Auf dem 233MHz-Test-System schaffte dieser
Beispiel-Code etwa 50 Remote-Message-Aufrufe pro Sekunde.
Wenn man das dann noch mit Suns JavaSpaces vergleicht, der Grundlage ihrer JINI-Architektur,
sieht man, dass drb mit einem kleinen Modul ausgeliefert wird, das genau das gleiche macht.
JavaSpaces basiert auf einer Technologie mit Namen Linda. Um zu zeigen, dass auch japanische
Autoren Sinn für Humor haben, heißt die Ruby-Version von Linda ``rinda.''
public
auf private
ändern und so weiter.
Man kann sogar Basis-Typen verändern wie Class
oder Object
.
Wenn man sich erst mal an diese Flexibilität gewöhnt hat, möchte man gar nicht mehr zurück zu statischen Sprachen
wie C++, auch nicht zu halb-statischen wie Java.
Warum auch?