|
|||
sin
, cos
usw. Sie stopfen sie alle in die Datei trig.rb
, damit auch spätere
Generationen Spaß daran haben können. In der Zwischenzeit arbeitet Sally an
einer Simulation von Gut und Böse und schreibt eine Reihe von nützlichen
Routinen wie beGood
und sin
(Sünde) und steckt sie in
die Datei action.rb
. Joe, der ein Programm schreiben möchte, um
herauszufinden, wie viele Engel auf dem Kopf einer Stecknadel tanzen können,
muss sowohl trig.rb
als auch action.rb
in sein Programm
laden. Beide definieren aber eine Methode namens sin
. Schlechte
Neuigkeiten.
Die Antwort ist der Modulmechanismus. Module können einen Namensraum
definieren, einen Sandkasten, in dem Ihre Methoden und Konstanten spielen
können, ohne befürchten zu müssen, dass sie von anderen Methoden und Konstanten
getreten werden. Die Trigonometriefunktionen wandern in ein Modul:
module Trig PI = 3.141592654 def Trig.sin(x) # .. end def Trig.cos(x) # .. end end |
module Action VERY_BAD = 0 BAD = 1 def Action.sin(badness) # ... end end |
require
, die wir auf Seite
105 besprechen) und auf die dort bestimmten Namen verweisen.
require "trig" require "action" y = Trig.sin(Trig::PI/4) wrongdoing = Action.sin(Action::VERY_BAD) |
module Debug |
||
def whoAmI? |
||
"#{self.type.name} (\##{self.id}): #{self.to_s}" |
||
end |
||
end |
||
class Phonograph |
||
include Debug |
||
# ... |
||
end |
||
class EightTrack |
||
include Debug |
||
# ... |
||
end |
||
ph = Phonograph.new("West End Blues") |
||
et = EightTrack.new("Surrealistic Pillow") |
||
|
||
ph.whoAmI? |
» | "Phonograph (#537683810): West End Blues" |
et.whoAmI? |
» | "EightTrack (#537683790): Surrealistic Pillow" |
Debug
erlangen sowohl
Phonograph
als auch EightTrack
Zugriff zur Instanzmethode whoAmI?
.
Bevor wir weitermachen, noch einige Punkte über die Anweisung
include
:
Erstens hat sie nichts mit Dateien zu tun. C-Programmierer verwenden eine
Präprozessor-Anweisung namens #include
, um während des Compilierens
den Inhalt einer Datei in eine andere einzufügen. In Ruby erstellt
include
aber einfach eine Referenz auf ein benanntes Modul. Wenn
dieses Modul in einer separaten Datei steht, muss diese zuerst mit
require
geladen werden, bevor include
benutzt werden kann.
Zweitens kopiert include
nicht einfach die Instanzmethoden eines
Moduls in die Klasse. Stattdessen erstellt Ruby eine Referenz von der Klasse
zum inkludierten Modul. Wenn mehrere Klassen dieses Modul inkludieren, zeigen
sie alle auf dasselbe Ding. Wenn Sie die Definition einer Methode innerhalb
eines Moduls ändern, auch während das Programm läuft, zeigen alle Klassen das
neue Verhalten.[Natürlich sprechen wir hier nur von Methoden.
Instanzvariablen zum Beispiel gelten immer jeweils für ein Objekt.]
Mixins stellen einen wunderbar kontrollierten Weg bereit, um Funktionalität zu
Klassen hinzuzufügen. Die wahre Kraft von Mixins kommt aber erst zur Geltung,
wenn der Code im Mixins mit dem in der Klasse, das das Mixin benutzt, zu
interagieren beginnt. Nehmen wir als Beispiel das Standardmixin
Comparable
. Dieses Mixin kann benutzt werden, um die
Vergleichsoperatoren (<
, <=
, ==
,
>=
und >
) sowie die Methode between?
zu
einer Klasse hinzuzufügen.
Damit das funktioniert, setzt Comparable
voraus, dass
der Benutzer den Operator <=>
definiert. Als Klassenschreiber
definieren Sie also die eine Methode <=>
, inkludieren
Comparable
und erhalten sechs Vergleichsmethoden
gratis. Probieren wir das mit unserer Klasse Song
aus,
indem wir die Lieder anhand ihrer Dauer vergleichen.
Alles, was wir tun müssen, ist das Modul Comparable
zu
inkludieren und den Vergleichsoperator <=>
zu implementieren.
class Song include Comparable def <=>(other) self.duration <=> other.duration end end |
song1 = Song.new("My Way", "Sinatra", 225) |
||
song2 = Song.new("Bicylops", "Fleck", 260) |
||
|
||
song1 <=> song2 |
» | -1 |
song1 < song2 |
» | true |
song1 == song1 |
» | true |
song1 > song2 |
» | false |
inject
aus Smalltalk gezeigt, die wir in die Klasse
Array
implementierten. Wir versprachen, dass wir diese
Funktion allgemeiner anwendbar machen würden. Gibt es einen besseren Weg, also
daraus ein Mixin Modul zu machen?
module Inject def inject(n) each do |value| n = yield(n, value) end n end def sum(initial = 0) inject(initial) { |n, value| n + value } end def product(initial = 1) inject(initial) { |n, value| n * value } end end |
class Array |
||
include Inject |
||
end |
||
[ 1, 2, 3, 4, 5 ].sum |
» | 15 |
[ 1, 2, 3, 4, 5 ].product |
» | 120 |
class Range |
||
include Inject |
||
end |
||
(1..5).sum |
» | 15 |
(1..5).product |
» | 120 |
('a'..'m').sum("Letters: ") |
» | "Letters: abcdefghijklm" |
Enumerable
an, die auf Seite
407 beginnt.
self
.
Für Mixins heißt das, dass das Modul, das Sie Ihrer Unterklasse beimischen,
Instanzvariablen im Unterobjekt erzeugen kann und attr
und
Ähnliches verwenden kann, um Zugriffsmethoden für diese Instanzvariablen zu
definieren. Ein Beispiel:
module Notes attr :concertA def tuning(amt) @concertA = 440.0 + amt end end class Trumpet include Notes def initialize(tune) tuning(tune) puts "Instance method returns #{concertA}" puts "Instance variable is [email protected]}" end end # Das Klavier ist ein bisschen tief: Trumpet.new(-5.3) |
Instance method returns 434.7 Instance variable is 434.7 |
module MajorScales def majorNum @numNotes = 7 if @numNotes.nil? @numNotes # Gib 7 zurück end end module PentatonicScales def pentaNum @numNotes = 5 if @numNotes.nil? @numNotes # Gib 5 zurück? end end class ScaleDemo include MajorScales include PentatonicScales def initialize puts majorNum # Sollte 7 sein puts pentaNum # Sollte 5 sein end end ScaleDemo.new |
7 7 |
@numNotes
. Unglücklicherweise entspricht das
Ergebnis nun wahrscheinlich nicht der Absicht des Autors.
Zum Großteil versuchen Mixin-Module nicht, eigene Instanzdaten mitzuführen --
sie verwenden Zugriffsmethoden, um Daten von der Unterklasse zu erhalten. Wenn
Sie aber ein Mixin erstellen, das seinen eigenen Status haben muss,
vergewissern Sie sich, dass die Instanzvariablen eindeutige Namen tragen, damit
sie von jedem anderen Mixin im System unterschieden werden können (vielleicht,
indem Sie den Namen des Moduls als Teil des Variablennamens verwenden).
Enumerable
, all diese megaaffentollen Fähigkeiten
unterstützen. Alles, was Sie tun müssen, ist einen Iterator namens
each
zu schreiben, der die Elemente Ihrer Sammlung zurückgibt.
Mischen Sie Enumerable
und plötzlich unterstützt Ihre
Klasse Dinge wie map
, include?
und
find_all?
. Wenn sich die Objekte ihrer Sammlung mit der Methode
<=>
auch sinnvoll ordnen lassen, können Sie weiters
min
, max
und sort
verwenden.
load "filename.rb" require "filename" |
load
fügt die genannte Datei mit Ruby Sourcecode
jedesmal, wenn Sie sie aufrufen, ein, während require
jede
gegebene Datei nur ein einziges Mal lädt.
require
hat zusätzliche Funktionalität: Es kann verteilte
Binärbibliotheken laden. Beide Routinen akzeptieren relative und absolute
Pfade. Wenn Sie einen relativen Pfad (oder nur einen einzelnen Dateinamen)
angeben, durchsuchen sie jedes Verzeichnis im aktuellen Load-Pfad
($:
, siehe Seite 142) nach der Datei.
Dateien, die mit load
und require
geladen wurden, können
natürlich auch andere Dateien inkludieren, die wiederum Dateien laden usw.
Nicht so offensichtlich ist, dass require
eine ausführbare Anweisung
ist -- require
kann innerhalb einer if
-Anweisung stehen oder
einen String enthalten, der gerade erst zusammengesetzt wurde. Auch der
Suchpfad kann während der Laufzeit verändert werden. Fügen Sie dazu einfach das
Verzeichnis, das sie möchten, an den String $:
hinzu.
Da load
den Source ohne Abfrage lädt, kann man es benutzen, um
eine Datei, die vielleicht seit dem Programmaufruf geändert wurde, erneut zu
laden:
5.times do |i| File.open("temp.rb","w") { |f| f.puts "module Temp\ndef Temp.var() #{i}; end\nend" } load "temp.rb" puts Temp.var end |
0 1 2 3 4 |