Neu: Das englische Ruby on Rails 4.0 Buch.

2.7. Noch mal Methoden

Um die Menge der Henne-Ei-Probleme in diesem Kapitel erträglich zu halten, müssen wir uns jetzt noch einmal mit Methoden beschäftigen und dabei das bis jetzt Erlernte kombinieren.

Getter und Setter

Da Instanz-Variablen (Attribute) nur innerhalb der jeweiligen Instanz existieren, muss man zum Exportieren einer solchen Variable immer eine Getter-Methode schreiben. Wenn wir eine Klasse Raum definieren, die als Instanz-Variablen @tueren und @fenster hat (für die Anzahl der Türen und Fenster des Raumes), so können wir wie folgt Getter-Methoden tueren und fenster erstellen (Beispielprogramm raum.rb):
class Raum
  def initialize
    @tueren  = 1   # ein neuer Raum hat eine Tür
    @fenster = 1   # ein neuer Raum hat ein Fenster
  end
  
  def tueren
    @tueren        # der Wert von @tueren wird zurückgegeben
  end
  
  def fenster
    @fenster       # der Wert von @fenster wird zurückgegeben
  end
end

wohnzimmer = Raum.new

puts wohnzimmer.tueren
puts wohnzimmer.fenster
Die Ausführung des Programmes:
sw@debian:~/sandbox$ ruby raum.rb 
1
1
sw@debian:~/sandbox$ 
Da dieses Szenario – dass man einfach nur einen Wert identisch zurückgeben will – so häufig auftritt, gibt es dafür schon eine vorgefertigte Getter-Methode namens attr_reader, die wie folgt im Programm raum.rb angewendet werden würde:
class Raum
  def initialize
    @tueren  = 1
    @fenster = 1
  end
  
  attr_reader :tueren, :fenster   # die Namen werden als Symbol angegeben
end

wohnzimmer = Raum.new

puts wohnzimmer.tueren
puts wohnzimmer.fenster

Anmerkung

attr_reader ist ein gutes Beispiel für Meta-Programmierung in Ruby. Sie werden gerade bei der Arbeit mit Rails immer wieder auf Meta-Programmierung stoßen und sich über die Automagie freuen.
Wenn man von außen die Anzahl der Türen oder Fenster verändern will, so braucht man dafür eine Setter-Methode. Die kann wie folgt realisiert werden:
class Raum
  def initialize
    @tueren  = 1
    @fenster = 1
  end
  
  attr_reader :tueren, :fenster
  
  def tueren=(value)       # Setter fuer tueren
    @tueren = value
  end
  
  def fenster=(value)      # Setter fuer fenster
    @fenster = value
  end
end

wohnzimmer = Raum.new

puts wohnzimmer.tueren
puts wohnzimmer.fenster

wohnzimmer.fenster = 2     # fenster wird neu gesetzt

puts wohnzimmer.fenster
Die entsprechende Ausgabe dazu:
sw@debian:~/sandbox$ ruby raum.rb
1
1
2
sw@debian:~/sandbox$ 
Sie können es sich wahrscheinlich schon denken: Natürlich gibt es dazu ebenfalls einen vorgefertigten und kürzeren Weg. Mit der Methode attr_writer können Sie den Code von raum.rb weiter vereinfachen:
class Raum
  def initialize
    @tueren  = 1
    @fenster = 1
  end
  
  attr_reader :tueren, :fenster
  
  attr_writer :tueren, :fenster
end

wohnzimmer = Raum.new

puts wohnzimmer.tueren
puts wohnzimmer.fenster

wohnzimmer.fenster = 2

puts wohnzimmer.fenster
Und (wer hätte das gedacht!) es gibt auch eine Methode attr_accessor, die Getter und Setter kombiniert. Der Code für raum.rb sähe dann so aus:
class Raum
  def initialize
    @tueren  = 1
    @fenster = 1
  end
  
  attr_accessor :tueren, :fenster
end

wohnzimmer = Raum.new

puts wohnzimmer.tueren
puts wohnzimmer.fenster

wohnzimmer.fenster = 2

puts wohnzimmer.fenster

Mitgelieferte Methoden bei String

Die meisten Klassen kommen von Hause aus schon mit einem ganzen Sack voll äußerst praktischer Methoden daher. Diese Methoden werden immer mit einem Punkt abgetrennt hinter dem entsprechenden Objekt geschrieben.
Hier ein paar Beispiele für Methoden der Klasse String.
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> a = 'Das Haus.'
=> "Das Haus."
1.9.3p194 :002:0> a.class     # Name der Klasse
=> String
1.9.3p194 :003:0> a.size      # Länge des Strings
=> 9
1.9.3p194 :004:0> a.downcase  # Umwandlung in Kleinbuchstaben
=> "das haus."
1.9.3p194 :005:0> a.upcase    # Umwandlung in Großbuchstaben
=> "DAS HAUS."
1.9.3p194 :006:0> a.reverse   # Buchstabenreihenfolge umdrehen
=> ".suaH saD"
1.9.3p194 :007:0> exit

Verketten von Methoden (Method chaining)

Man kommt nicht direkt darauf, aber wenn man sich an das Arbeiten mit Ruby gewöhnt hat, dann ist es auch völlig selbstverständlich (weil logisch), verschiedene Methoden zu verketten.
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> a = 'Das Haus.'
=> "Das Haus."
1.9.3p194 :002:0> a.class
=> String
1.9.3p194 :003:0> a.upcase.reverse  # Großbuchstaben umgedreht
=> ".SUAH SAD"
1.9.3p194 :004:0> exit

Von einem zum anderen konvertieren

Logischerweise gibt es eine ganze Reihe von praktischen Instanz-Methoden, um Objekte von einer Klasse in eine andere zu wandeln (zu casten). Als Erstes verwandeln wir mit der Methode .to_s ein Fixnum in einen String.
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> a = 10
=> 10
1.9.3p194 :002:0> a.class
=> Fixnum
1.9.3p194 :003:0> b = a.to_s
=> "10"
1.9.3p194 :004:0> b.class
 => String 
1.9.3p194 :005:0> exit
Nichts anderes macht übrigens puts, wenn man mit puts ein Fixnum oder ein Float ausgibt (es fügt bei Nicht-Strings einfach implizit die Methode .to_s hinzu und gibt das Ergebnis aus).
Jetzt wandeln wir mit der Methode .to_i ein Float in ein Fixnum um.
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> c = 10.0
=> 10.0
1.9.3p194 :002:0> c.class
=> Float
1.9.3p194 :003:0> d = c.to_i
=> 10
1.9.3p194 :004:0> d.class
=> Fixnum
1.9.3p194 :005:0> exit

Methode to_s bei eigenen Klassen

Man sollte – und wenn es nur für das einfachere Debugging ist – bei selbstdefinierten Klassen immer eine Methode to_s einbauen. Dann kann man ein entsprechendes Objekt einfach mit puts ausgeben (puts gibt ein Objekt automatisch unter Verwendung der Methode to_s aus).
Ein Beispiel:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> class Tier
1.9.3p194 :002:1>   def initialize(de_name,en_name)
1.9.3p194 :003:2>     @de_name = de_name
1.9.3p194 :004:2>     @en_name = en_name
1.9.3p194 :005:2>   end
1.9.3p194 :006:1>   def to_s
1.9.3p194 :007:2>     @de_name + ' (' + @en_name + ')'
1.9.3p194 :008:2>   end
1.9.3p194 :009:1> end
=> nil
1.9.3p194 :010:0> lumpi = Tier.new('Hund', 'Dog')
=> #<Tier:0x873b5c8 @de_name="Hund", @en_name="Dog">
1.9.3p194 :011:0> hansi = Tier.new('Wellensittich', 'Budgerigar')           
=> #<Tier:0x86b60f8 @de_name="Wellensittich", @en_name="Budgerigar">
1.9.3p194 :012:0> puts lumpi
Hund (Dog)
=> nil
1.9.3p194 :013:0> puts hansi
Wellensittich (Budgerigar)
=> nil
1.9.3p194 :014:0> exit

Ist + eine Methode?

Wieso steht bei der Liste der Methoden beim String auch das Pluszeichen? Schauen wir mal mit ri nach:
sw@debian:~/sandbox$ ri String.+

(from ruby site)
------------------------------------------------------------------------------
  str + other_str   -> new_str

------------------------------------------------------------------------------

Concatenation---Returns a new String containing other_str
concatenated to str.

  "Hello from " + self.to_s   #=> "Hello from main"


sw@debian:~/sandbox$
Hmmm … mal sehen, was da bei Fixnum steht:
sw@debian:~/sandbox$ ri Fixnum.+
(from ruby site)
------------------------------------------------------------------------------
  fix + numeric  ->  numeric_result

------------------------------------------------------------------------------

Performs addition: the class of the resulting object depends on the class of
numeric and on the magnitude of the result.

sw@debian:~/sandbox$
Spielen wir damit einmal im irb. Wir müssten ja dann das + genauso wie jede andere Methode mit einem Punkt an das Objekt hängen können und die zweite Zahl in einer Klammer als Parameter anfügen:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> 10 + 10
=> 20
1.9.3p194 :002:0> 10+10
=> 20
1.9.3p194 :003:0> 10.+10
=> 20
1.9.3p194 :004:0> 10.+(10)
=> 20
1.9.3p194 :005:0> exit
Aha! Das Pluszeichen ist tatsächlich eine Methode, und diese Methode nimmt den nächsten Wert als Parameter. Eigentlich müssten wir diesen Wert in einer Klammer setzen, aber Ruby erspart uns das mit durchdachter Syntax.

Kann ich die Methode + überschreiben?

Ja, man kann alle Methoden überschreiben. Logischerweise macht das bei Methoden wie + wenig Sinn, außer man will damit seine Programmierkollegen in den Wahnsinn treiben. Damit Sie mir glauben, zeige ich jetzt im irb eine kleine Demo.
Das Ziel ist es, die Methode + für Fixnum zu überschreiben. Als Ergebnis jeder Addition soll die Zahl 42 herauskommen.
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> 10 + 10
=> 20
1.9.3p194 :002:0> 10 + 5555
=> 5565
1.9.3p194 :003:0> class Fixnum 
1.9.3p194 :004:1>   def +(name, *args, &blk)
1.9.3p194 :005:2>     42
1.9.3p194 :006:2>   end
1.9.3p194 :007:1> end
=> nil
1.9.3p194 :008:0> 10 + 10
=> 42
1.9.3p194 :042:0> 10 + 5555
=> 42
1.9.3p194 :042:0> exit
In den Zeilen 1 und 2 führen wir die normale Addition durch. In den Zeilen 3 bis 7 definieren wir die Methode + für die Class Fixnum neu, und danach führen wir noch einmal die Berechnung durch. Dann allerdings mit anderen Ergebnissen.

Liste aller Methoden für eine bestimmte Klasse

Nun hat man nicht immer alle Methoden-Namen im Kopf, weiß aber, dass es für das spezielle Problem eine perfekte Methode gibt (passiert mir ständig).
Im irb
Mit der Methode methods kann man die zur Verfügung stehenden Methoden anzeigen. Allerdings weiß man dann nicht unbedingt, was diese genau machen.
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> a = 'Das blaue Haus.'
=> "Das blaue Haus."
1.9.3p194 :002:0> a.methods
=> [:<=>, :==, :===, :eql?, :hash, :casecmp, :+, :*, :%, :[], :[]=, :insert, :length, :size, :bytesize, :empty?, :=~, :match, :succ, :succ!, :next, :next!, :upto, :index, :rindex, :replace, :clear, :chr, :getbyte, :setbyte, :to_i, :to_f, :to_s, :to_str, :inspect, :dump, :upcase, :downcase, :capitalize, :swapcase, :upcase!, :downcase!, :capitalize!, :swapcase!, :hex, :oct, :split, :lines, :bytes, :chars, :codepoints, :reverse, :reverse!, :concat, :<<, :crypt, :intern, :to_sym, :ord, :include?, :start_with?, :end_with?, :scan, :ljust, :rjust, :center, :sub, :gsub, :chop, :chomp, :strip, :lstrip, :rstrip, :sub!, :gsub!, :chop!, :chomp!, :strip!, :lstrip!, :rstrip!, :tr, :tr_s, :delete, :squeeze, :count, :tr!, :tr_s!, :delete!, :squeeze!, :each_line, :each_byte, :each_char, :each_codepoint, :sum, :slice, :slice!, :partition, :rpartition, :encoding, :force_encoding, :valid_encoding?, :ascii_only?, :unpack, :encode, :encode!, :to_r, :to_c, :>, :>=, :<, :<=, :between?, :nil?, :!~, :class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :define_singleton_method, :__id__, :object_id, :to_enum, :enum_for, :gem, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__]
1.9.3p194 :003:0> exit