Eigene Klassen
Nachdem wir in zwei einfachen Beispielen die Benutzung von Standard- Klassen und -Iteratoren gesehen haben, werden wir uns nun anschauen, wie man in Ruby selbst Klassen und später Iteratoren definiert.
Um auch speziellere Eigenschaften von Klassen in Ruby zu zeigen, betrachten wir die Implementierung einer Klasse, deren Instanzen drei stabile Zustände einnehmen können: die Tribble-Klasse. Warum keine gängige Sprache einen derartigen Datentyp vorzuweisen hat, ist schon merkwürdig. Solche Klassen sind nämlich keineswegs unsinnig, kann man doch beispielsweise mit einer Klasse und den Zuständen soll, muss und egal recht gut die Wünsche eines Kunden in Bezug auf einen Artikel ausdrücken - um dann den optimalen Gegenstand für genau diesen Kunden herauszusuchen.
Oder wie wäre es beispielsweise mit einer Klasse, die uns durch die Zustände ja, nein und jein bei schwierigen Entscheidungen hilft? Im Folgenden definieren wir zuerst eine allgemeine Klasse Tribble, von der wir dann zu Testzwecken die Klasse JNTribble ableiten, die uns bei Entscheidungen helfen wird. Betrachten Sie Listing 1 für eine erste einfache Klasse Tribble.
Listing 1: Die Tribble-Klasse
|
class Tribble # Großbuchstabe vorne!
def initialize # Konstruktor
@zustaende = Array.new # mögl. Zustände
@zustand = nil # default-Zustand
end
def setValues( arr ) # erlaubte Werte eintragen
if (arr.type.to_s == "Array")
arr.uniq! # doppelte Elemente killen
@zustaende = arr if arr.size == 3
end
end
protected :setValues
def get # Methode, Auslesen d. Zustands
@zustand
end
def set( neu ) # Methode, Zustand setzen
@zustand = neu if@zustaende.include?(neu)
end
def ==(other) # Vergleichsoperator
return nil if get.nil? or other.get.nil?
@zustand == other.get
end
end
|
Der Name der Klasse muss mit einem Großbuchstaben beginnen. Mit def definiert man eine Methode, initialize ist der reservierte Name für den Konstruktor. Dort legen wir die Instanzvariablen @zustaende (Möglichkeiten) und @zustand (aktueller Zustand) an, auf die innerhalb einer Instanz alle Methoden zugreifen können.
Die Methode setValues dient dazu, die möglichen Zustände des Tribbles festzulegen. Diese Methode sei protected, damit sie nur in Tribble und abgeleiteten Klassen benutzbar ist. .get dient dazu, den aktuellen Zustand auszulesen, .set(wert) setzt den aktuellen Zustand. In .get steht kein return, es kann weggelassen werden, da der Wert des letzten Ausdrucks automatisch der Rückgabewert der Methode ist.
Die dritte Methode schließlich definiert den ==-Operator für die Tribble-Klasse: Ist der Zustand eines der Tribbles nil, dann ist auch das Ergebnis nil, sonst werden die Zustände der beiden beteiligten Tribbles verglichen und als Ergebnisse true beziehungsweise false zurückgegeben.
Die .nil?-Methode ist in der Klasse Object definiert und liefert true nur dann, wenn das Objekt, dessen nil?-Methode aufgerufen wird, nil ist. Anders gesagt: Eine Variable ist niemals undefiniert, sondern mindestens eine Instanz der nil-Klasse.
Um eine verwendbare Klasse zum Spielen zu haben, leiten wir von Tribble die neue Klasse JNTribble ab, die uns mit Analysefunktionen das Leben leichter macht. Die neue Klasse soll die Funktionalität der Tribble-Klasse haben und die Zustände ja, nein und jein einnehmen können.
Bei Ruby darf eine Klasse jeweils nur eine andere Klasse beerben. Um weitere Methoden in einer Klasse verfügbar zu machen, könnte man Module einbinden, die außerhalb der Klasse definiert sind. In unserer neuen Klasse (Listing 2) definieren wir einen neuen Konstruktor, der den Konstruktor der Elternklasse aufruft und die möglichen Zustände von Instanzen unserer Klasse festlegt. Dabei kann optional ein Startzustand angegeben werden, default ist ja.
Außerdem definieren wir einen neuen Operator +, der zwei Instanzen von JNTribble verknüpft und abhängig von den Zuständen eine neue JNTribble-Instanz liefert. Die Logik lässt sich durch die Verwendung von Statement-Modifiers in drei Zeilen unterbringen: Wenn zwei zu vergleichende Tribbles ja sind, ist das Ergebnis ja, aus ja + nein wird jein und aus dem Rest nein.
Damit steht nun eine verwendbare Tribble-Klasse für erste Tests zur Verfügung. Dazu instanzieren wir das JNTribble zweimal und vergleichen die Inhalte der Objekte:
t1 = JNTribble.new("ja")
t2 = JNTribble.new("nein")
print "Vergleich ", t1 == t2, "n"
print "ja + nein = "
t3 = t1 + t2 # ja + nein = ?
puts t3.get
Die Ausgabe ist:
Vergleich: false
ja + nein = jein
Ist ja klar, denn ja + nein ergibt nun mal ein glasklares jein, was sonst. Die Anweisung puts gibt den übergebenen String gefolgt von n aus.
Iteratoren im Eigenbau
Wo bleiben die versprochenen Iteratoren? Nun, wir bauen uns jetzt einen Container für JNTribbles, HTArray, mit einem Iterator each, der über alle Elemente (Tribbles) iterieren kann, ohne den Anwender mit lästigen Details über die interne Speicherung der Tribbles zu belästigen. Außerdem bauen die beiden Methoden add(object) und get(num) zum Einspeichern beziehungsweise Auslesen von Tribbles ein.
Listing 3 zeigt eine einfache Implementierung, die nicht nur Tribbles einspeichern würde (Arrays können beliebige Objekte speichern) und die man in diesem Fall eigentlich auch durch ein normales Array ersetzen könnte. Die Verwendung einer eigenen Klasse mit Iterator hat jedoch den Vorteil, dass wir später problemlos die klasseninterne Realisierung der Speicherung ändern könnten, während die Schnittstelle nach außen gleich bliebe.
Listing 2: JNTribble - Das definierte Jein
|
class JNTribble < Tribble # erbt alle Methoden
def initialize(zustand="ja") # optionaler defaultwert
super() # ruft die methode der Elternklasse auf.
setValues( ["ja", "nein", "jein" ] ) # Array on the fly...
@zustand = zustand if@zustaende.include?(zustand)
end
def +(other) # ein selbstdefinierter Operator: t2 = t1 + t2
return JNTribble.new("ja") if (<@>zustand == "ja") and (other.get == "ja")
return JNTribble.new("jein") if (<@>zustand == "ja") or (other.get == "ja")
return JNTribble.new("nein") end
end
|
Die Definition der Iterator-Methode ist relativ simpel: Man schreibt eine Methode, die nacheinander alle gespeicherten Objekte an die Hand nimmt und an das yield-Kommando übergibt. Bei der Verwendung des Iterators tritt dann für jedes Objekt der übergebene Codeblock an die Stelle des yield, wobei die Parameter des yield-Kommandos in den in |...| angegebenen Variablen landen.
Listing 3: Ein Array aus Tribbles
|
class HTArray
def initialize
@arr = Array.new # Daten in Array
end
def add( tribble ) # tribble anhängen
@arr.push( tribble )
end
def get ( num ) # tribble auslesen
@arr[num]
end
def each # Iterator über alle tribbles
@arr.each {
|tribble|
yield tribble # block mit tribble aufrufen
}
end
end
|
Listing 3 zeigt eine mögliche Implementierung von HTArray mit einem .each-Iterator. Mit folgendem Codeschnipsel können Sie das HTArray ausprobieren. Wenn Sie die Listings zu JNTribble und HTArray in einzelnen Dateien abgelegt haben, müssen Sie die Klassendefinitionen mittels require einbinden:
require `jntribble.rb'
require `htarray.rb'
ha = HTArray.new
ha.add( JNTribble.new("ja") )
ha.add( JNTribble.new("jein") )
ha.add( JNTribble.new("nein") )
print "Vergleiche `ja' mit allen Werten:n"
ha.each {
|t| # yield tribble, tribble -> t
print ((t + JNTribble.new("ja")).get, "n")
}
Vorher hatten wir die Klammern bei print weggelassen, sobald jedoch die Ausdrücke hinter print kompliziert werden, sollte man nicht auf Klammern verzichten, damit der Interpreter weiß, welcher Teil des Textes hinter print ein Parameter für den Aufruf ist. In Zweifelsfällen lassen sich mit Hilfe von ruby -w missverständliche Stellen im Quellcode anzeigen.
| Whitepaper |
|
Open Source Datenintegration in der Praxis: Fallstudien und Anwendungsbeispiele (Folge 2)
Der zweite Teil des Open Source Datenintegration in der Praxis: Fallstudien und Anwendungsbeispiele White Papers beleuchtet anhand weiterer ausgewählter Case Studies die Implementierung von Open Source Datenintegration in der Praxis und benennt die daraus resultierenden Vorteile.
Download PDF (Registrierung erforderlich)
|
|
Usage Landscape Enterprise Open Source Data Integration
Die Nachfrage nach Datenintegrationslösungen für Unternehmen ist zunehmend gestiegen und vor allem das Interesse an Open Source Technologien wird immer größer. Doch wie und von wem werden Open Source Datenintegrationslösungen genutzt und welches Nutzungsverhalten lässt sich daraus ableiten? Das vorliegende White Paper präsentiert die Erfahrungswerte von über 1000 Open Source Nutzern und liefert fundierte Antworten auf diese Fragen.
Download PDF (Registrierung erforderlich)
|
Dieser Online-Artikel kann Links enthalten, die auf nicht mehr vorhandene Seiten verweisen. Wir ändern solche "broken links"
nur in wenigen Ausnahmefällen. Der Online-Artikel soll möglichst unverändert der gedrucken Fassung entsprechen.
|