Klassen
Bisher haben wir verschiedene Arten, bzw. Klassen, von Objekten kennengelernt:
Strings, Integers, Floats, Arrays und ein paar Sonderfälle (true, false und nil), über die wir später reden.
In Ruby beginnen diese Klassen immer mit einem Großbuchstaben: String, Integer, Float, Array... und so weiter.
Wollen wir eine neues Objekt einer gewissen Klasse erzeugen, verwenden wir das Schlüsselwort new:
a = Array.new + [12345] # Array addition b = String.new + 'hello' # String addition c = Time.new puts 'a= '+a.to_s puts 'b= '+b.to_s puts 'c= '+c.to_s
a= 12345 b= hello c= 2011-06-05 18:21:21 +0100
Da man Array-Objekte mit [...] und String-Objekte mit '...' erzeugen kann,
verwendet man in diesem Zusammenhang selten new.
(Allerdings erzeugt in obigem Beispiel Array.new ein leeres Array
und String.new einen leeren String.)
Auch Integer bilden einen Sonderfall: doch kann man einen Integer nicht mit Integer.new erzeugen,
sondern muss die entsprechende Zahl schreiben.
Die Klasse Time
Was steckt nun hinter dieser Klasse Time?
Time-Objekte stellen Zeitpunkte dar.
Man kann Zahlen hinzufügen oder abziehen, um ein neues Time-Objekt zu erhalten.
Wenn man 1.5 zu einem Time-Objekt hinzuzählt, wird ein neues Time-Objekt erzeugt,
das den Moment anderthalb Sekunden später darstellt:
time = Time.new # Zeitpunkt, zu dem ich dieses Programm geschrieben habe time2 = time + 60 # eine Minute später puts time puts time2
Tue Jun 07 12:34:22 +0200 2011 Tue Jun 07 12:35:22 +0200 2011
Man kann auch ein Time-Objekt für einen gegebenen Zeitpunkt erstellen:
puts Time.mktime(2000, 1, 1) # 1.Januar 2000 puts Time.mktime(1976, 8, 3, 10, 11) # mein Geburtstag
Sat Jan 01 00:00:00 +0100 2000 Tue Aug 03 10:11:00 +0100 1976
Bemerkung: Die Zeitangaben gelten bezüglich der am Computer eingestellten Zeitzone. Die Klammern dienen dazu, die Parameter zu gruppieren. Je mehr Parameter man angibt, desto genauer wird die Zeit angegeben.
Man kann Zeitem mit Vergleichsmethoden vergleichen (ein früherer Zeitpunkt ist kleiner als ein späterer) und wenn man eine Zeit von einer anderen abzieht, erhält man die Anzahl der Sekunden zwischen den Zeitpunkten. Spiel einfach mal damit herum!
Selber ausprobieren
- Eine Milliarde Sekunden... Ermittel die genaue Sekundenzahl bei deiner Geburt (wenn du das weißt). Berechne, wann du eine Milliarde Sekunden alt wird (oder wurdest). Markier es dir in deinem Kalender :-)
- Alles Gute zum Geburtstag! Frage eine Person nach dem Jahr, dem Monat und dem Tag ihrer Geburt, berechne, wie alt sie ist, und gib einen Glückwunsch für jedes Jahr aus!
Die Hash-Klasse
Eine weitere nützliche Klasse ist die Klasse Hash. Hashes sind Arrays sehr ähnlich: sie haben viele Felder, die auf andere Objekte zeigen können. Im Array liegen diese Felder hinter einander und sind nummeriert (bei 0 anfangend). Im Hash sind diese Felder in keiner Reihenfolge (sie sind nur irgendwie da) und man kann jedes Objekt verwenden, um ein Feld anzusprechen. Ein typische Anwendungsfall liegt vor, wenn man eine Menge von Objekten verwalten will, aber keine Reihenfolge gegeben ist, zum Beispiel die Farben, die ich für verschiedene Teile in einem längeren Text verwenden könnte:
farbArray = [] # neues Array, wie Array.new
farbHash = {} # neues Has, wie Hash.new
farbArray[0] = 'rot'
farbArray[1] = 'grün'
farbArray[2] = 'blau'
farbHash['string'] = 'rot'
farbHash['zahlen'] = 'grün'
farbHash['schlüssel'] = 'blau'
farbArray.each do |farbe|
puts farbe
end
farbHash.each do |code, farbe|
puts code + ': ' + farbe
end
rot grün blau schlüssel: blau zahlen: grün string: rot
Wenn ich im Beispiel ein Array verwende, muss ich mir merken, dass ich im Feld 0 die Farbe für Strings gespeichert habe, im Feld 1 die Farbe für Zahlen und so weiter. Einfacher geht's mit Hashes: Das Feld 'string' enthält die Farbe für Strings, das Feld 'zahlen' enthält die Farbe für Zahlen und das Feld 'schlüssel' enthält die Farbe für Ruby-Schlüsselwörter. Man muss sich nichts merken. Vielleicht hast du im Beispiel festgestellt, dass die Objekte im Hash nicht in derselben Reihenfolge ausgegeben wurden, in der wir sie hineingeschrieben haben. Arrays behalten die Reihenfolge bei, Hashes nicht.
Obwohl es meist geschickt ist, Strings zu verwenden, um die Felder in einem Hash zu bezeichnen, kann man jedes Objekt von jeder Klasse verwenden, sogar Arrays oder andere Hashes (der Sinn ist dann vielleicht fraglich...):
komisch = Hash.new komisch[12] = 'Affen' komisch[[]] = 'totale Leere' komisch[Time.new] = 'ich hab keine Zeit'
Hashes und Arrays haben jeweils ihre Vorteile. Du musst selber entscheiden, was am besten zu deiner Situation passt.
Klassen erweitern
Am Ende des letzten Kapitels haben wir ein Beispiel geschrieben, in dem wir das deutsche Wort für eine Zahl zusammengebaut haben.
Es war keine Methode, die zur Klasse Integer gehört, sondern nur irgendeine Programm-Methode.
Wäre es nicht nett, wenn man etwa 22.to_deu schreiben könnte, statt deutscheZahl 22?
Das geht so:
class Integer
def to_deu
if self == 5
deutsch = 'fünf'
else
deutsch = 'achtundfünfzig'
end
deutsch
end
end
# machen wir den Test:
puts 5.to_deu
puts 58.to_deu
fünf achtundfünfzig
Ich hab's ausprobiert und es funktioniert ;-)
Wir haben eine Integer Methode definiert, indem wir in die Integer-Klasse hineingesprungen sind,
die Methode definiert haben und wieder herausgesprungen sind.
Jetzt haben alle Integer-Objekte diese Methode.
Wenn du die Art und Weise, wie to_s funktioniert, nicht magst, kannst du die Methode ebenso neu schreiben...
was ich hier aber nicht raten würde!
Am besten läßt man alte Methoden in Ruhe und fügt neue hinzu, wenn man etwas neues machen will.
Verwirrt?
Schauen wir uns das letzte Programm noch einmal in Ruhe an.
Wenn wir bisher eine Methode geschrieben haben, war sie immer im Standard-Programm-Kontext abgelegt.
In diesem Programm hingegen verlassen wir diesen Kontext und gehen in die Klasse Integer,
in der wir die Methode definieren (was sie zu einer Integer-Methode macht) und alle Integers können sie nutzen.
Innerhalb der Methode nutzen wir das Wort self um auf das aktuelle Objekt zu verweisen.
Eigene Klassen schreiben
Wir haben schon eine Reihe verschiedener Klassen und Objekte gesehen.
Dennoch ist es nicht schwer, sich eine Klasse zu überlegen, die von Ruby nicht angeboten wird.
Glücklicherweise ist das Schreiben einer neuen Klasse genau so einfach wie das Erweitern einer bestehenden.
Angenommen, wir würden gerne mit Würfeln in Ruby arbeiten.
Eine Klasse Wuerfel könnten wir zum Beispiel so schreiben:
class Wuerfel
def wuerfeln
1 + rand(6)
end
end
# Erzeugen wir mal zwei Würfel...
wuerfel = [Wuerfel.new, Wuerfel.new]
# ...und werfen wir sie:
wuerfel.each do |w|
puts w.wuerfeln
end
5 2
(Wenn du den Abschnitt über Zufallszahlen ausgelassen hast: rand(6) liefert uns eine zufällige Zahl von 0 bis 5.)
Und da haben wir es auch schon: eine Klasse für unsere eigenen Objekte.
Wir können beliebige Methoden für unsere Objekte definieren... aber etwas fehlt noch: diese KLassen sind ein bisschen wie unsere Programme bevor wir Variablen kennengelernt haben. Schauen wir uns zum Beispiel die Würfel an: wir können sie werfen und jedes Mal erhalten wir eine neue Zahl, aber wir können später nicht mehr auf die geworfene Zahl zugreifen. Dazu bräuchten wir eine Variable, die auf diese Zahl zeigt. Verglichen mit der Realität sollte jeder Würfel eine Zahl haben und das Würfeln sollte diese Zahl verändern.
Wenn wir nun versuchen, die gewürfelte Zahl in einer (lokalen) Variablen der Methode wuerfeln zu speichern,
wird der Inhalt der Variablen weg sein, sobald die Methode wuerfeln beendet ist.
Diese Zahl müssen wir in einer anderen Art von Variablen speichern:
Instanz-Variablen
Wenn wir normalerweise über einen String sprechen, sagen wir einfach String. Wir könnten auch String-Objekt sagen. Manche Programmierer würden es eine Instanz der Klasse String nennen, was aber recht lang ist. Unter einer Instanz einer Klasse versteht man ein Objekt dieser Klasse.
Eine Instanz-Variable ist eine Variable eines Objektes.
Die lokalen Variablen von Methoden existieren nur bis die Methode beendet ist.
Instanzvariablen eines Objektes hingegen bestehen so lange, wie das Objekt existiert.
Um Instanzvariablen von lokalen Variablen unterscheiden zu können, wird ihnen ein @ vorangestellt:
class Wuerfel
def wuerfeln
@zahl = 1 + rand(6)
end
def zeigen
@zahl
end
end
wuerfel = Wuerfel.new
wuerfel.wuerfeln
puts wuerfel.zeigen
puts wuerfel.zeigen
wuerfel.wuerfeln
puts wuerfel.zeigen
puts wuerfel.zeigen
5 5 1 1
Sehr schön! Nun würfelt die Methode wuerfeln den Würfel und die Methode zeigen zeigt uns, welche Zahl gewürfelt worden ist.
Allerdings: was passiert, wenn wir zeigen aufrufen, bevor gewürfelt worden ist (bevor @zahl überhaupt gesetzt worden ist)?
class Wuerfel
def wuerfeln
@zahl = 1 + rand(6)
end
def zeigen
@zahl
end
end
puts Wuerfel.new.zeigen
... wenigstens keine Fehlermeldung!
Aber dennoch macht es keinen Sinn, dass ein Würfel keine gewürfelte Zahl anzeigt.
Es wäre schöner, wenn jedem Würfel-Objekt eine gewürfelte Zahl zugewiesen wird, wenn es erzeugt wird.
Dafür gibt es die Methode initialize:
class Wuerfel
def initialize
wuerfeln
end
def wuerfeln
@zahl = 1 + rand(6)
end
def zeigen
@zahl
end
end
puts Wuerfel.new.zeigen
3
Wenn ein Object erzeugt wird, wird diese Methode initialize aufgerufen, wenn es eine hat.
Unser Würfel ist quasi perfekt. Das einzige, was vielleicht fehlen könnte, wäre die Möglichkeit festzulegen, welche Zahl der Würfel gerade anzeigt... warum schreibst du nicht eine Methode, die genau das macht? Komm wieder, wenn du fertig bist und dein Programm erfolgreich getesten hast. Stelle auch sicher, dass niemand den Würfel auf 7 stellen kann!
Ziemlich coole Sachen, die wir hier gerade gemacht haben...
echt tricky. Deshalb machen wir jetzt noch ein Beispiel.
Angenommen, wir wollen ein virtuelles Tier bauen, etwa einen Babydrachen.
Wie die meisten Babys kann er essen, schlafen und pinkeln.
Dies bedeutet, dass wir ihn füttern müssen, schlafen legen und spazieren schieben.
Intern merkt sich unser Drache, ob er gerade hungrig oder müde ist oder aufs Töpfchen muss.
Aber das sehen wir von außen nicht, wie bei einem echten Baby kann man nicht fragen "Bist du hungrig?".
Ein paar lustige Sachen kommen noch hinzu und natürlich bekommt unser Drachen einen Namen, wenn er geboren wird.
(Was der new Methode übergeben wird, wird an initialize weitergegeben.)
Ok, los geht's:
class Drache
def initialize name
@name=name
@schlafen=false
@magen=10 # =nicht hungrig, 0=hungrig!
@blase=0 # =muss nicht pinkeln, 10=muss pinkeln!
puts @name + ' wird geboren.'
end
def fuettern
puts @name + ' wird gefuettert.'
@magen=10
zeitvergeht
end
def spazieren
puts @name + ' wird spazieren gefahren.'
@blase=0
zeitvergeht
end
def schlafenlegen
puts @name + ' wird schlafen gelegt.'
@schlafen=true
3.times do
if @schlafen
zeitvergeht
end
if @schlafen
puts @name + ' schnarcht, es raucht und qualmt.'
end
end
if @schlafen
@schlafen=false
puts @name + ' wacht langsam auf.'
end
end
def hochwerfen
puts 'Du wirfst ' + @name + 'hoch in die Luft.'
puts @name + 'lacht und versengt Deine Augenbrauen.'
zeitvergeht
end
def schaukeln
puts 'Du schaukelst ' + @name + ' sanft.'
@schlafen = true
puts @name + ' schlaeft ein.'
zeitvergeht
if @schlafen
@schlafen = false
puts '... wacht aber wieder auf, wenn Du aufhoerst.'
end
end
private
# private bedeutet, dass die Methoden, die nun definiert werden,
# nur innerhalb des Objektes sichtbar sind und aufgerufen werden können.
def hungrig?
# Methodennamen dürfen mit ? enden.
# Das macht man aber nur, wenn die Methode true oder false zurück gibt.
@magen<=2
end
def blasevoll?
@blase>=8
end
def zeitvergeht
if @magen >0
# Nahrung bewegt sich vom Magen in die Blase
@magen = @magen-1
@blase = @blase+1
else
# Drache hungert!
if @schlafen
@schlafen=false
puts @name + ' wacht ploetzlich auf!'
end
puts @name + ' hat einen Riesenhunger... und frisst DICH!!!'
exit # beendet das Programm
end
if @blase>=10
@blase=0
puts 'oha - ' + @name + ' macht auf den Boden.'
end
if hungrig?
if @schlafen
@schlafen = false
puts @name + ' wacht auf.'
end
puts @name + ' hat Magenknurren.'
end
if blasevoll?
if @schlafen
@schlafen = false
puts @name + ' erwacht ploetzlich.'
end
@name + ' geht aufs Toepfchen.'
end
end
end
kuscheltier = Drache.new 'Norbert'
kuscheltier.fuettern
kuscheltier.hochwerfen
kuscheltier.spazieren
kuscheltier.schlafenlegen
kuscheltier.schaukeln
kuscheltier.schlafenlegen
kuscheltier.schlafenlegen
kuscheltier.schlafenlegen
kuscheltier.schlafenlegen
kuscheltier.schlafenlegen
Norbert wird geboren. Norbert wird gefuettert. Du wirfst Norberthoch in die Luft. Norbertlacht und versengt Deine Augenbrauen. Norbert wird spazieren gefahren. Norbert wird schlafen gelegt. Norbert schnarcht, es raucht und qualmt. Norbert schnarcht, es raucht und qualmt. Norbert schnarcht, es raucht und qualmt. Norbert wacht langsam auf. Du schaukelst Norbert sanft. Norbert schlaeft ein. ... wacht aber wieder auf, wenn Du aufhoerst. Norbert wird schlafen gelegt. Norbert wacht auf. Norbert hat Magenknurren. Norbert wird schlafen gelegt. Norbert wacht auf. Norbert hat Magenknurren. Norbert wird schlafen gelegt. Norbert wacht auf. Norbert hat Magenknurren. Norbert wird schlafen gelegt. Norbert wacht ploetzlich auf! Norbert hat einen Riesenhunger... und frisst DICH!!!
Wow! Natürlich wäre es noch schöner, wenn es ein interaktives Programm wäre, aber das kannst du ja später selber machen. Ich wollte dir nur die wichtigen Teile zeigen, die man für eine Drachen-Klasse braucht.
Wir haben in diesem Beispiel ein paar neue Dinge gesehen:
Das erste ist einfach: exit beendet das Programm.
Als zweites haben wir das Wort private kennengelernt, das mitten in der Drachen-Klassen-Definition steckt.
Man könnte es auch weglassen, doch ich wollte dir klar machen, was der Unterschied ist zwischen
den Methoden, die von außen aufgerufen werden können (also Dingen, die mit dem Drachen gemacht werden können)
und Methoden, die nur innerhalb des Drachens abspielen.
Man kann sich diese auch als unter der Motorhaube vorstellen:
wenn du nicht Automechaniker bist, musst du dich nur um Gaspedal, Bremse und Lenkrad kümmern.
Ein Programmierer bezeichnet dies als die öffentliche Schnittstelle deines Autos.
Doch wie genau der Airbag herausbekommt, wann er auslösen muss, liegt im Inneren des Autos,
der typische Autofahrer weiß das nicht.
Um ein konkreteres Beispiel heranzuziehen, denken wir mal über ein Auto in einem Videospiel nach: Erst einmal sollte man entscheiden, wie die öffentliche Schnittstelle aussehen sollte, in anderen Worten, was sollen die Spieler mit dem Auto machen können. Also, sie sollen das Gas- und das Bremspedal treten können, und sie müsssen irgendwie sagen können, wie kräftig sie das Pedal treten. (Denn es ist ein Unterschied, ob man es nur leicht berührt oder richtig durchtritt.) Außerdem müssen sie lenken können und auch da ist es wieder interessant, wie weit sie das Lenkrad einschlagen wollen. Und so weiter und so fort... was genau man dem Benutzer ermöglichen möchte, hängt von der Art des Spiels ab.
Aber innerhalb des Autos muss man noch mehr Daten und Methoden verwalten: die aktuelle Geschwindigkeit, die Richtung, die Position, und vieles mehr. Diese Attribute werden verändert durch die öffentlichen Methoden, aber der Benutzer kann sie nicht direkt setzen. Dies geschieht innerhalb des Autos.
Selber ausprobieren
-
Schreibe eine Klasse OrangenBaum.
Sie soll eine Methode
hoehehaben, die die Höhe zurückgibt, und eine Methodeeinjahrvergeht, welche den Baum um ein Jahr altern läßt. Jedes Jahr wird der Baum höher und nach einer bestimmten Anzahl von Jahren stirbt der Baum. In den ersten Jahren trägt er keine Früchte, doch nach einer Weile schon und ältere Bäume trägen mehr Früchte als junge Bäume... wie es für dich Sinn macht. Und natürlich musst du auch die Orangenzaehlenund eine Orangepflueckenkönnen. Stell sicher, dass alle Orangen, die du nicht erntest, vor dem nächsten Jahr herunterfallen. -
Schreib ein interaktives Programm für den Babydrachen.
Dabei sollst du Kommandos eintippen, wie
fuetternoderspazieren, und die entsprechenden Methoden des Drachens werden aufgerufen. Weil du nur Strings eingibst, muss du dafür irgendwie die richtigen Methoden zuweisen.
Und das war's auch schon... halt, Moment, ich habe dir noch nichts erzählt über all die Klassen in Ruby, die Emails verschicken, Dateien speichern oder laden, Fenstern oder Buttons anzeigen oder gar 3D-Welten oder so...! Also, es gibt soooo viele Klasse, dass ich dir unmöglich alles zeigen kann. Die meisten kenne ich selber nicht! Ich kann dir aber noch ein paar Tips geben, wo du weitere Informationen finden kannst, damit du genau über die KLassen nachlesen kannst, die du zum Programmieren brauchst. Es gibt nur noch ein Thema, das ich dir zeigen möchte... ein Thema, das die meisten Programmiersprachen nicht haben, ohne das ich aber nicht mehr leben könnte: Blocks und Procs.