|
|||
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?