Eine Klasse ist im Grunde nichts anderes als ein Behälter für Variablen und Methoden, die auf diese Variablen zugreifen oder sie verändern. Klassen vereinfachen Code enorm, anstatt einen Berg an Variablen zu erzeugen, kann man einfach ein Objekt dieser Klasse erzeugen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
#Erzeugung eines zweidimensionalen Arrays
dimension_1 = []
dimension_2 = []
zweidimensionales_array = [dimension_1, dimension_2]
#Oder kurz:
zweidimensionales_array = [[nil, nil, nil],[nil, nil, nil]] #Größe ist 3x3.
#Zuweisen eines Wertes an das Array:
zweidimensionales_array[1][2] = 5
#Abrufen des Wertes
puts zweidimensionales_array[1][2] #=> 5
#----------------------------------------------------------------------------------------
#Oder gar ein dreidimensionales Array:
dreidimensionales_array = [[[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]], [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]], [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]] #Größe ist 3x3x3.
dreidimensionales_array[0][1][0] = 7
puts dreidimensionales_array[0][1][0] #=> 7
#----------------------------------------------------------------------------------------
#Mit einer Klasse lässt sich das einfacher lösen:
class DreiD_Array
attr_reader :breite, :hoehe, :tiefe #Gettermethoden
def initialize(breite, hoehe, tiefe)
#Instanzvariablen zuweisen
@drei_d = []
@breite = breite
@hoehe = hoehe
@tiefe = tiefe
#Leeres, verschachteltes Array erstellen
breite.times do |br|
@drei_d << []
hoehe.times do |ho|
@drei_d[br] << []
tiefe.times do |ti|
@drei_d[br][ho] << nil
end
end
end
end
#Eleganten Zugriff auf das Array erlauben
def [](x, y, z)
return @drei_d[x][y][z]
end
def []=(x, y, z, wert)
@drei_d[x][y][z] = wert
return wert
end
def first
return self.[](0, 0, 0)
end
def size
"#{@breite}x#{@hoehe}x#{@tiefe} = #{@breite * @hoehe * @tiefe}."
end
def inspect #Spezialmethode, die immer aufgerufen wird, wenn ein Objekt mit p ausgegeben wird
"Größe:\n#{size}\nInhalt:\n#{@drei_d.inspect}"
end
end
#Jetzt kann man, sooft man will, 3D-Arrays erstellen, ohne noch einmal die aufwändige Erstellprozedur durchzumachen.
drei_d = DreiD_Array.new(3, 3, 3)
drei_d[0, 1, 0] = 7
puts drei_d[0, 1, 0] #=> 7
p drei_d
#=> Größe:
#=> 3x3x3 = 27
#=> Inhalt:
#=> [...]
drei_d2 = DreiD_Array.new(10, 10, 10)
drei_d2[7, 4, 3] = 99
drei_d2[9, 9, 9] = "Test"
puts drei_d2[7, 4, 3] #=> 99
puts drei_d2.first #=> nil |
Klassendefinition
Zuerst wird eine Klasse definiert. Dazu wird das Schlüsselwort class benutzt. Klassen beginnen per Konvention in Ruby immer mit einem Großbuchstaben, deshalb halten auch wir uns daran und nennen die Klasse DreiD_Array.
Initialize-Methode
Darauf folgt (wenn nötig) mit def die Definition einer Methode. initialize ist eine spezielle Methode: Sie wird jedes Mal aufgerufen, wenn eine Instanz der Klasse (mit new) erzeugt wird.
Das mit @ bezeichnete Wort ist eine Instanzvariable. Diese Instanzvariablen haben einen speziellen Gültigkeitsbereich, nämlich lediglich ihre eigene Klasse. Sie können in den Methoden der Klasse abgerufen werden, aber nicht von außerhalb. Subklassen können allerdings die Instanzvariablen ihrer Superklassen verwenden. Instanzvariablen sind für jede Instanz einer Klasse anders. Eine Instanzvariable muss nicht definiert werden; ruft man eine bisher nicht definierte Instanzvariable ab, so erhält man nil.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class A
def set_a(wert)
@a = wert
end
def get_a
return @a
end
end
x = A.new
x.set_a(50)
x.get_a #=> 50
y = A.new
y.get_a #=> nil
#---
@a = 5
puts @a #=> 5
puts @b #=> nil |
Instanzmethoden
Wie so gut wie jede andere Klasse besitzt auch unsere
DreiD_Array-Klasse „normale“ Methoden, Instanzmethoden. Ähnlich wie die Instanzvariablen besitzen auch sie nur einen eingeschränkten Gültigkeitsbereich, die Klasse. Diese Methoden können von innerhalb der Klasse aufgerufen werden, wie in der Methode
first[1] oder aber von einer Instanz der Klasse, wie im obigen Beispiel die Zeile
.
Klassenmethoden
Neben den Instanzmethoden gibt es noch Klassenmethoden. Sie werden weniger häufig verwendet, können aber durchaus auch nützlich sein. Eine Klassenmethode kann man auf vier Arten definieren:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
class A
def A.methode
puts "Klassenmethode!"
end
end
A.methode #=> Klassenmethode!
#---
class A
def self.methode
puts "Klassenmethode!"
end
end
A.methode #=> Klassenmethode!
#---
def A.methode
puts "Klassenmethode!"
end
A.methode #=> Klassenmethode!
#---
class A
class << self
def methode
puts "Klassenmethode!"
end
end
end
A.methode #=> Klassenmethode! |
Die letzten beiden sind dabei besonders, sie nutzen aus, dass in Ruby jedes Objekt (auch Klassen sind Objekte) eine Singletonklasse besitzt, dessen einzige Instanz es ist. Das wird im Artikel für Singletons ausführlicher behandelt.
Genauso wie es Klassenmethoden gibt, gibt es Klassenvariablen, sie werden mit @@ definiert. Diese Variablen sind komplett auf ihre Klasse ausgelegt, nicht auf die Instanzen. Eine Klassenvariable ist für alle Instanzen die Gleiche:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class A
@@anzahl_instanzen = 0
@@klassenvariable = 0
def initialize
@@anzahl_instanzen += 1
end
def self.anzahl_instanzen
@@anzahl_instanzen
end
def +(zahl)
@@klassenvariable += 1
end
def wie_viel?
@@klassenvariable
end
end
A.new
A.new
A.new
puts A.anzahl_instanzen #=> 3
x = A.new
y = A.new
x + 99
puts y.wie_viel? #=> 99 |
Getter- und Settermethoden
Als Getter- bzw. Setter-Methode bezeichnet man Methoden, die Instanzvariablen einer Klasse les- bzw. schreibbar machen. Eine Getter-Methode, die die Breite unseres dreidimensionalen Arrays lesbar macht, wäre zum Beispiel folgende:
1
2
3
4
5
|
class DreiD_Array
def width
@breite
end
end |
Für all diese Instanzvariablen in einer Klasse aber Getter- und Settermethoden anzulegen, wäre eine leidige Arbeit, die den Code unnötig verlängern würde, deshalb gibt es bereits vordefinierte, so genannte »Decorator-Methoden«, die diese Aufgabe erledigen. Diese Methoden sind attr_reader, attr_writer und attr_accessor. Sie erstellen anhand der Symbole, die ihnen mitgegeben werden, Getter- und/oder Settermethoden für die durch diese Symbole beschriebenen Instanzvariablen.
attr_reader(*syms): Erzeugt eine Gettermethode für alle durch die mitgegebenen Symbole beschriebenen Instanzvariablen.
attr_writer(*syms): Erzeugt eine Settermethode für alle durch die mitgegebenen Symbole beschriebenen Instanzvariablen. Wird selten verwendet.
attr_accessor(*syms): Erzeugt sowohl eine Getter- als auch eine Settermethode für alle durch die mitgegebenen Symbole beschriebenen Instanzvariablen.
Diese Methoden funktionieren nicht für Klassenvariablen!
1
2
3
4
5
6
7
8
9
|
class A
attr_accessor :b
def initialize
@b = 1
end
end
x = A.new
x.b = 5
x.b #=> 5 |
Methodenzugriff
Wie viele andere Programmiersprachen, besitzt auch Ruby Möglichkeiten, die Zugriffsrechte auf Methoden zu verändern. Die Methoden – ja, Methoden – dazu heißen public, private und protected. Standardmäßig sind alle in einer Klasse definierten Methoden public.
| Methode | Zugriff von außen | Vererbbar | Zugriff innerhalb der Klasse
|
| public | Ja | Ja | Ja
|
| private | Nein | Ja | Ja
|
| protected | Nein | Nein | Ja
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
class A
def do_b_and_d
b
d
end
private
def b
puts "b - private!"
end
public
def c
puts "c - public!"
end
protected
def d
puts "d - protected!"
end
end
class X < A
end
x = A.new
x.b #=> NoMethodError
x.c #=> c - public!
x.d #=> NoMethodError
x.do_b_and_d
#=> b - private!
#=> d - protected!
y = X.new
y.b #=> NoMethodError
y.c #=> c - public!
y.d #=> NoMethodError
y.do_b_and_d
#=> b - private
#=> NoMethodError |
Es ist auch über einen Trick möglich, geschützte oder private Methoden von außen über Object#send aufzurufen. Das sollte man aber nur mit gutem Grund tun.
1
2
3
4
5
6
7
8
9
|
class A
def b
puts "b - private!"
end
private :b # -> so kann man auch eine methode ändern
end
x = A.new
x.b #=> NoMethodError
x.send(:b) #=> b - private |
Offene Klassen
Rubys Klassen sind offen. Das heißt, man kann sie nach ihrer Definition wieder öffnen, Methoden überschreiben, hinzufügen, entfernen etc.
1
2
3
4
5
6
|
class Array
def shuffle
sort_by{rand}
end
end
[1, 2, 3].shuffle #=> [2, 3, 1] |
Fußnoten
- [1]In der Methode
first wird der Methodenaufruf mit self aufgerufen – das ist aber in der Regel nicht notwendig, es reicht, wenn man den Methodennamen schreibt. Hier ist es nur erforderlich, weil [] vom Interpreter sofort als Arraydefinition aufgefasst wird.
Siehe auch