Programmierung in Ruby

Der Leitfaden der Pragmatischen Programmierer

Mehr über Methoden



Andere Sprachen haben Funktionen, Prozeduren, Methoden oder Routinen, aber in Ruby gibt es nur die Methode -- ein Bündel Anweisungen, die einen Wert zurückgeben.

In diesem Buch haben wir bis jetzt, ohne viel darüber nachzudenken, Methoden definiert und benutzt. Jetzt ist es an der Zeit, sich die Details anzusehen.

Methoden definieren

Wie wir schon im ganzen Buch hindurch gesehen haben, wird eine Methode durch das Schlüsselwort def definiert. Methodennamen sollten mit einem Kleinbuchstaben beginnen. [Sie erhalten nicht sofort eine Fehlermeldung, wenn Sie einen Großbuchstaben benutzen. Wenn Sie aber die Methode aufrufen, denkt Ruby zuerst, dass es sich um eine Konstante und nicht um einen Methodenaufruf handelt und könnte den Aufruf falsch parsen.] Namen von Methoden, die für Abfragen zuständig sind, enden oft mit einem ``?'', so wie instance_of?. ``Gefährliche'' Methoden oder solche, die den Empfänger modifizieren, können auf ``!'' enden. String zum Beispiel stellt die Methoden chop und chop! bereit. Die erste retourniert den veränderten String; die zweite verändert den Empfänger. ``?'' und ``!'' sind die einzigen Sonderzeichen, die als Suffices für Methodennamen erlaubt sind.

Nun, da wir einen Namen für unsere neue Methoden angegeben haben, sollten wir einige Parameter deklarieren. Parameter sind einfach ein Liste von Namen lokaler Variablen, die in Klammern angegeben werden. Einige Beispiele für Methodendeklarationen sind

def myNewMethod(arg1, arg2, arg3)     # 3 Argumente
  # Code für die Methode würde hier stehen
end

def myOtherNewMethod                  # Keine Argumente   # Code für die Methode würde hier stehen end

In Ruby kann man Defaultwerte für die Argumente einer Methode spezifizieren -- Werte, die verwendet werden, wenn sie beim Aufruf nicht explizit übergeben werden. Dies wird mit Hilfe des Zuweisungsoperators gemacht.

def coolDude(arg1="Miles", arg2="Coltrane", arg3="Roach")
  "#{arg1}, #{arg2}, #{arg3}."
end
coolDude » "Miles, Coltrane, Roach."
coolDude("Bart") » "Bart, Coltrane, Roach."
coolDude("Bart", "Elwood") » "Bart, Elwood, Roach."
coolDude("Bart", "Elwood", "Linus") » "Bart, Elwood, Linus."

In der Methode selbst stehen normale Anweisungen, mit der Ausnahme, dass man keine Instanzmethoden, Klassen oder Module innerhalb einer Methode definieren kann. Der Rückgabewert einer Methode ist der Wert des zuletzt ausgeführten Ausdrucks oder das Ergebnis eines expliziten Aufrufs von return.

Argumentlisten variabler Länge

Was aber, wenn Sie eine variable Anzahl von Argumenten übergeben wollen oder mehrere Argumente mit einem einzigen Parameter auffangen wollen? Wenn Sie einen Stern vor den Namen des Parameters nach den ``normalen'' Parametern setzen, wird genau das gemacht.

def varargs(arg1, *rest)
  "Got #{arg1} and #{rest.join(', ')}"
end
varargs("one") » "Got one and "
varargs("one", "two") » "Got one and two"
varargs "one", "two", "three" » "Got one and two, three"

In diesem Beispiel wird das erste Argument wie üblich dem ersten Methodenparameter übergeben. Da dem nächsten Parameter jedoch ein Stern vorangestellt ist, werden alle verbleibenden Parameter in einem neuen Array zusammengefasst, das diesem Parameter zugewiesen wird.

Methoden und Blöcke

Wie schon im Abschnitt über Blöcke und Iteratoren von Seite 40 an besprochen, kann ein Methodenaufruf mit einem Block assoziiert werden. Normalerweise ruft man den Block innerhalb der Methode einfach mit yield auf.

def takeBlock(p1)
  if block_given?
    yield(p1)
  else
    p1
  end
end

takeBlock("no block") » "no block"
takeBlock("no block") { |s| s.sub(/no /, '') } » "block"

Ist dem letzten Parameter in der Definition einer Methode jedoch ein Ampersand vorangestellt, wird ein assoziierter Block in ein Proc-Objekt konvertiert und dem Parameter zugewiesen.

class TaxCalculator
  def initialize(name, &block)
    @name, @block = name, block
  end
  def getTax(amount)
    "[email protected] on #{amount} = #{ @block.call(amount) }"
  end
end
tc = TaxCalculator.new("Sales tax") { |amt| amt * 0.075 }
tc.getTax(100) » "Sales tax on 100 = 7.5"
tc.getTax(250) » "Sales tax on 250 = 18.75"

Methoden aufrufen

Sie rufen eine Methode auf, indem Sie den Namen des Empfängers, den Namen der Methode und optional einige Parameter und einen assoziierten Block angeben.

connection.downloadMP3("jitterbug") { |p| showProgress(p) }

In diesem Beispiel ist das Objekt connection der Empfänger, downloadMP3 der Name der Methode, "jitterbug" der Parameter und das Zeugs zwischen den geschwungenen Klammern der assoziierte Block.

Für Methoden von Klassen und Modulen ist der Empfänger der Name der Klasse oder des Moduls.

File.size("testfile")
Math.sin(Math::PI/4)

Lässt man dem Empfänger aus, wird er auf self, das aktuelle Objekt, gesetzt.

self.id » 537712200
id » 537712200
self.type » Object
type » Object

Mit diesem Mechanismus (dass nämlich beim Auslassen des Empfängers dieser automatisch self ist) implementiert Ruby private Methoden. Private Methoden dürfen nicht mit einem Empfänger aufgerufen werden, daher müssen sie Methoden sein, auf die nur das aktuelle Objekt zugreifen kann.

Die optionalen Parameter folgen dem Methodenname. Gibt es keine Doppeldeutigkeiten, kann man die Klammern um die Argumentliste herum auslassen, wenn man eine Methode aufruft. [Andere Dokumentationen zu Ruby nennen diese Methodenaufrufe ohne Klammern manchmal ``Befehle.''] Außer in den simpelsten Fällen empfehlen wir das jedoch nicht -- es gibt einige Probleme, über die man dabei stolpern kann. [Insbesonders muss man Klammern verwenden, wenn ein Methodenaufruf selbst wieder Parameter eines anderen Methodenaufrufs ist (außer, es ist der letzte Parameter).] Unsere Regel ist einfach: Wenn Sie im Zweifel sind, verwenden Sie Klammern.

a = obj.hash    # Das gleiche wie
a = obj.hash()  # das.

obj.someMethod "Arg1", arg2, arg3   # Das gleiche wie obj.someMethod("Arg1", arg2, arg3)  # mit Klammern.

Arrays in Methodenaufrufen expandieren

Vorher haben wir gesehen, dass, wenn man einen Stern vor einem formalen Parameter in der Methodendefinition setzt, mehrere Argumente im Methodenaufruf zu einem Array zusammengefasst werden. Gut, das gleiche funktioniert auch umgekehrt.

Wenn Sie eine Methode aufrufen, können Sie ein Array aufspalten, sodass jedes seiner Elemente als ein separater Parameter genommen wird. Die geschieht, wenn Sie einem Arrayargument (das allen anderen regulären Parametern folgen muss) einen Stern voranstellen.

def five(a, b, c, d, e)
  "I was passed #{a} #{b} #{c} #{d} #{e}"
end
five(1, 2, 3, 4, 5 ) » "I was passed 1 2 3 4 5"
five(1, 2, 3, *['a', 'b']) » "I was passed 1 2 3 a b"
five(*(10..14).to_a) » "I was passed 10 11 12 13 14"

Dynamischere Blöcke

Wir haben bereits gesehen, wie man einen Block mit einem Methodenaufruf assoziieren kann.

listBones("aardvark") do |aBone|
  # ...
end

Normalerweise reicht das völlig aus -- Sie assoziieren einen festen Codeblock mit einer Methode, wie Sie auch Code nach if oder while angeben würden.

Manchmal jedoch möchte man etwas flexibler sein. Beispielsweise möchten wir Mathematik lehren.[Natürlich müssten Andy und Dave zuerst einmal Mathe lernen. Conrad Schneiker erinnerte uns daran, dass es drei Arten von Leuten gibt: Die, die zählen können, und die, die's nicht können.] Der Schüler könnte nach einer n-Additions- und einer n-Multiplikationstabelle fragen. Fragt er nach einer Multiplikationstabelle zur Zahl 2, würden wir 2, 4, 6, 8 usw. ausgeben (Dieser Code überprüft die Eingabe nicht auf Fehler).

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i

if times =~ /^t/   puts((1..10).collect { |n| n*number }.join(", ")) else   puts((1..10).collect { |n| n+number }.join(", ")) end
produces:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

Das Beispiel funktioniert, aber es ist hässlich, mit fast identischem Code an jeder Verzweigung der if-Anweisung. Es wäre nett, wenn wir den Block, der die Berechnung durchführt, ausklammern könnten.

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i

if times =~ /^t/   calc = proc { |n| n*number } else   calc = proc { |n| n+number } end puts((1..10).collect(&calc).join(", "))
produces:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

Wenn dem letzten Argument einer Methode ein Ampersand vorangestellt ist, nimmt Ruby an, dass es sich um ein Objekt der Klasse Proc handelt. Ruby entfernt das Argument von der Liste, konvertiert das Proc-Objekt in einen Block und assoziiert ihn mit der Methode.

Diese Technik kann benutzt werden, um etwas ``syntactic sugar'' zur Verwendung von Blöcken hinzuzufügen. Beispielsweise möchte man manchmal einen Iterator nehmen und jeden Wert, den er mit yield zurückgibt, in einem Array speichern. Dafür verwenden wir unseren Fibonaccizahlen-Generator von Seite 42 nochmals.

a = []
fibUpTo(20) { |val| a << val } » nil
a.inspect » "[1, 1, 2, 3, 5, 8, 13]"

Dieses Beispiel funktioniert, aber man erkennt unsere Absicht nicht so gut, wie wir es gerne möchten. Stattdessen definieren wir nun eine Methode namens into, die einen Block zurückgibt, der das Array füllt. (Beachten Sie gleichzeitig, dass der zurückgegebene Block eigentlich ein Closure ist -- er referenziert den Parameter anArray, auch nachdem die Methode into beendet wurde.)

def into(anArray)
  return proc { |val| anArray << val }
end
fibUpTo 20, &into(a = [])
a.inspect » "[1, 1, 2, 3, 5, 8, 13]"

Hash-Argumente sammeln

Manche Sprachen kennen ``Schlüsselwortargumente'' -- das heißt, anstatt Argumente in gegebener Reihenfolge und Menge anzugeben, übergibt man den Namen des Arguments mit seinen Werten in beliebiger Reihenfolge. Ruby 1.6 hat diese Möglichkeit nicht (obwohl es geplant ist, sie in Ruby 1.8 zu implementieren).

In der Zwischenzeit können Sie Hashes verwenden, um den selben Effekt zu erzielen. Wir könnten zum Beispiel in Erwägung ziehen, eine mächtigere, benannte, Suchfunktion in unsere Klasse SongList einzubauen.

class SongList
  def createSearch(name, params)
    # ...
  end
end
aList.createSearch("short jazz songs", {
                   'genre'            => "jazz",
                   'durationLessThan' => 270
                   } )

Der erste Parameter ist der Suchname und der zweite ein Hash mit Suchparametern. Die Verwendung von Hashes ermöglicht es, Wörter zu simulieren: Such' nach Liedern des Genres ``Jazz'' und einer Dauer von weniger als 4 1/2 Minuten. Dieser Ansatz ist jedoch etwas clunky, und die geschwungenen Klammern könnten leicht als assoziierter Block missverstanden werden. Daher hat Ruby eine Abkürzung. Sie können Schlüssel => Wert Paare in einer Argumentliste angeben, solange sie jedem normalen Argument folgen und jedem Array und Block vorangehen. All diese Paare werden in einem einzigen Hash gesammelt und der Methode als ein Argument übergeben. Klammern werden nicht gebraucht.

aList.createSearch("short jazz songs",
                   'genre'            => "jazz",
                   'durationLessThan' => 270
                   )


Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Übersetzung: Johannes Tanzler
Für das englische Original:
© 2000 Addison Wesley Longman, Inc. Released under the terms of the Open Publication License V1.0. That reference is available for download.
Diese Lizenz sowie das Original vom Herbst 2001 bilden die Grundlage der Übersetzung
Es wird darauf hingewiesen, dass sich die Lizenz des englischen Originals inzwischen geändert hat.
Für die deutsche Übersetzung:
© 2002 Jürgen Katins
Der Copyright-Eigner stellt folgende Lizenzen zur Verfügung:
Nicht-freie Lizenz:
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder. Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.
Freie Lizenz:
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".