Lektion 16 - Objekte - na endlich!

Erstellt von Frithjof Sat, 17 Nov 2007 00:13:00 GMT

In den vergangenen Lektionen drehte sich alles um das Spiel Tic-Tac-Toe. Wir haben wie verrückt Ruby Code getippt und – so hoffe ich – fleißig Tic-Tac-Toe gespielt, zu zweit am und gegen den Computer. Der ist im Verlauf unserer Tätigkeiten ziemlich stark geworden. Irgendwie macht es dann mit der Zeit keinen Spaß mehr, weil man ja sowieso schon weiß, dass es wieder mal auf ein Unentschieden hinaus läuft.

Hier nochmal alle Lektionen zu dem Spiel im Überblick.

Und jetzt? Wir schauen uns in dieser Lektion etwas an, wovon sicher viele meinen, ich hätte es dir schon eher sagen sollen.

Machen wir uns ein wenig darüber Gedanken, wie wir bisher programmiert haben. Schau dir mal die letzte Datei tictactoe.rb an. Sie besteht aus zahlreichen Methoden (es müssten 16 Stück sein), die immer mit def gefolgt von einem Namen und einer Liste von Übergabeparametern anfangen und mit end abgeschlossen werden. Jede Methode ist eine Art kleines Progrämmchen, das einen kleinen Teil der Arbeit des gesamten Programms übernimmt. Sie wird von irgendwo aufgerufen, erledigt ihre Teilaufgabe und gibt möglicherweise ein Ergebnis an den Aufrufer zurück. Statt Methode sagen viele auch Prozedur dazu. Prozedur leitet sich vom Lateinischen Wort für vorwärts gehen ab.

Du stellst dir eine Methode (oder Prozedur) somit als eine Menge von aufeinander folgenden Programmanweisungen vor, die vom Rubyinterpreter der Reihe nach abgearbeitet werden. Der Rubyinterpreter geht gewissermaßen vorwärts durch die Codezeilen und macht, was sie ihm befehlen.

Wirf nochmal einen Blick in die Datei tictactoe.rb, insbesondere die ersten Zeilen der Methoden:


  def spielfeld(out, zuege)
  def print_zeile(out, zeile, zuege)
  def print_feld(spalte, zeile, zuege)
  def zug_hinzu(wer, spalte, zeile, zuege)
  def nummer_in_spalte_zeile(num)
  def nummer_aus_spalte_zeile(spalte, zeile)
  def the_winner_is(zuege)
  def ist_beendet?(zuege)
  def play_2_spieler(out, ein, zuege)
  def zufalls_zug(zuege, spieler, wer)
  def naiver_zug(zuege, spieler, wer)
  def freie_felder(zuege)
  def reihen_status(zuege, reihe)
  def intelligenter_zug(zuege, spieler, wer)
  def computer_zug(zuege, spieler, wer)
  def play_gegen_computer(out, ein, zuege)

Fällt dir etwas auf? Stichwort: Wiederholungen? Von den 16 Methoden, haben 14 die Variable zuege in ihrer Liste der Übergabeparameter! Nur auf zwei Methoden nummer_in_spalte_zeile und nummer_aus_spalte_zeile trifft das nicht zu.

Die Menge der Methoden lässt sich somit in zwei Gruppen einteilen: Methoden, die von der Liste der im Spiel gemachten zuege abhängen (die 14 Stück) und Methoden, die nicht von zuege abhängen (die 2 übrigen).

Die 14 abhängigen Methoden benötigen Wissen über die gemachten Spielzüge, daher muss man ihnen diese beim Aufruf mitgeben. Würden wir das Spiel noch weiter programmieren, beispielsweise mit einer grafischen Oberfläche versehen, dann hätten wir noch viel mehr Methoden zu implementieren – ziemlich alle wohl mit der Variablen zuege in der Liste der Übergabeparameter. Irgendwann fängt so eine Art Wiederholung an zu nerven. Dieses Phänomen ist sehr charakteristisch für prozedurale Programmierung, also Software, die nur mit isolierten Prozeduren arbeitet.

Irgendjemand hat sich irgendwann einmal überlegt, das zu ändern und die objektorientierte Programmierung erfunden. Das ist schon ziemlich lange her. Die Frage ist somit durchaus berechtigt, warum ich dir dies nicht von Anfang an erzählt habe und wir uns bis jetzt ausschließlich mit Prozeduren (oder Methoden) abgegeben haben?

Meine Antwort darauf: Man kapiert objektorientierte Programmierung einfach nicht von Anfang an! Das ging mir bei meinen ersten Programmierübungen so und das wird bei dir nicht anders sein. Über objektorientierte Programmierung zu sprechen lohnt sich wirklich erst, wenn man mit prozeduraler Programmierung sich einen Teil einer Programmiersprache erobert hat. Potenzrechnen versteht man erst, wenn man die Multiplikation verstanden hat, und diese wiederum erst, wenn Addition kein Fremdwort mehr ist. Also alles schön der Reihe nach.

Was ist nun aber objektorientierte Programmierung genau?

Schauen wir wieder auf unser Spiel Tic-Tac-Toe. Die Liste der zuege sind die wichtigsten Daten des Spiels. Die 14 von ihr abhängigen Methoden arbeiten immer mit oder auf diesen Daten. Die objektorientierte Programmierung bietet die Möglichkeit, solche Daten und ihre abhängigen Methoden dicht zusammenzuhalten (man sagt auch kapseln). In der prozeduralen Programmierung ist die Datei selbst das einzige Mittel, Daten und Methoden zusammenzuhalten. Das erste wichtige Merkmal der objektorientierten Programmierung (das schreibe ich jetzt zum letzten Mal aus, ab sofort verwende ich die gebräuchliche Abkürzung OOP) ist somit Kapselung.

Für das Spiel Tic-Tac-Toe könnte das etwa so aussehen:


class TicTacToe

  def initialize()
    @zuege = []
  end

  def spielfeld(out)
    # ...
  end

  def print_zeile(out, zeile)
    # ...
  end

  def print_feld(spalte, zeile)
    # ...
  end

  # ... restliche Methoden
end

Zwischen dem beginnenden class und dem zugehörigen end erfolgt die Kapselung von Daten (die zuege) und der Methoden, die auf den Daten arbeiten.

Warum aber erfolgt die Kapselung bei OOP mit dem Schlüsselwort class und nicht objekt (oder englisch object)? Wäre es nicht einleuchtender, wenn wir es so schreiben würden:


object TicTacToe

  def initialize()
    @zuege = []
  end

  # ... restliche Methoden
end

Zwischen einer Klasse (englisch class) und einem Objekt (englisch object) gibt es in OOP einen feinen Unterschied: Die Klasse ist der Bauplan der Objekte! Stelle es dir vor wie ein Architekt, der eine ganze Wohnsiedlung mit einem einzigen Haustyp baut. Er hat nur einen Bauplan eines Hauses. Mit diesem einen Bauplan kann er aber soviele Häuser bauen wie er möchte. Alle Häuser sehen gleich aus. Naja, nicht wirklich gleich. Das eine hat vielleicht rote Fensterläden, das andere grüne. Aber der Haustyp ist bei allen gleich, weil ihnen allen derselbe Bauplan zugrunde liegt.

Das OO Programmieren besteht also darin, Baupläne zu entwerfen. Den späteren Bau der Objekte sieht man nicht wirklich, der erfolgt nach diesen Bauplänen dann vom Rubyinterpreter. Lass dir ruhig etwas mit diesem Gedanken Zeit, wenn du es nicht sofort verstehst. Das ist normal. Mit jedem Stück OO Code wirst du es besser verstehen.

Schauen wir uns noch die anderen Besonderheiten der neuen Schreibweise an.

Konstruktor oder Geburtshelfer

In der Klasse TicTacToe haben wir eine Methode initialize definiert (abgeleitet vom lateinischen initio – anfangs), die wir bisher überhaupt nicht benötigten. Jede Klasse kann diese Methode besitzen, muss aber nicht. Initialize hat eine besondere Bedeutung: sie enthält die Anweisungen, die ganz am Anfang ausgeführt werden, wenn ein Objekt der Klasse das Licht der Welt erblickt. Daher nennt man diese Methode auch den Konstruktor der Klasse, also der Bereich, der dem Objekt alles für einen guten Start ins rauhe OO-Leben verpasst.


# Klasse definieren
class TicTacToe
  def initialize()
    puts "Hurra, ich bin da!" 
  end
end

# Ein Objekt der Klasse anlegen
spiel = TicTacToe.new

Führst du obiges Programm aus, erscheint als Ausgabe der Text Hurra, ich bin da! auf der Kommandozeile.

Die Methode initialize wird nie direkt aufgerufen, sondern indirekt von der Methode new. Innerhalb dieser Methode new wird irgendwann dann die Methode initialize aufgerufen und danach ist das Objekt für den weiteren Gebrauch fertig.

Wo aber kommt die Methode new denn nun schon wieder her, die haben wir doch nirgends definiert!

Methoden von Objekten und Klassen

Die Methode new ist eine weitere besondere Methode, die Ruby jeder Klasse automatisch verpasst. Jeder Klasse, sage ich! Nicht jedem Objekt der Klasse!

Eine Klassenmethode gibt es für alle Objekte einer Klasse immer nur ein einziges mal. Eine Objektmethode hat jedes Objekt dagegen für sich. Du erkennst den Unterschied daran, dass einer Klassenmethode der Name der Klasse vorangestellt wird, während vor einer Objektmethode der Name des Objekts steht—jeweils mit einem Punkt vom Methodennamen abgetrennt.

Attribute

Die Variable zuege ist aus allen Methoden verschwunden. Stattdessen steht sie direkt im Konstruktor mit einem @-Zeichen davor. Das @ markiert diese Variable als ein Attribut. Attribute sind die Variablen einer Klasse, die die Daten enthalten. Man nennt die Attribute auch Eigenschaften der Klasse. Für den außenstehenden sind diese Attribute nicht zugänglich, sie sind in der Klasse versteckt, verkapselt.

Beispiel für OOP

Am Beispiel der Klasse Haus wollen wir uns das bisher gehörte etwas genauer anschauen:


# Definition der Klasse 'Haus'
class Haus
  def initialize(f, fd)
    @farbe      = f
    @farbe_dach = fd
  end
  def to_s
    "Haus[farbe=#{@farbe}, farbe_dach=#{@farbe_dach}]" 
  end
end

# Anlegen von zwei Objekten der Klasse 'Haus'
h1 = Haus.new("gelb",  "rot")
h2 = Haus.new("braun", "rot")

puts h1.to_s
puts h2.to_s

Der Konstruktor wird von außen mit zwei Variablen aufgerufen: die Farbe des Hauses und die Farbe des Daches. Diese Angaben merkt sich jedes Objekt in seinen Attributen @farbe und @farbe_dach. Wird die Methode to_s für ein Objekt aufgerufen, so erhält man eine Zeichenkette, die diese Werte dieser Attribute enthält.


C:\entwicklung> ruby lektion_16.rb
Haus[farbe=gelb, farbe_dach=rot]
Haus[farbe=braun, farbe_dach=rot]

Du solltest nach dieser Lektion heute nicht einfach zur nächsten weitergehen, solange du die beiden Zeilen


puts h1.to_s
puts h2.to_s

noch nicht verstanden hast. Dir muss klar werden, warum in beiden Zeilen zwar dieselbe Methode to_s aufgerufen wird, aber beide Aufrufe nicht dasselbe Ergebnis liefern.

Es liegt daran, dass es sich hier eben nicht mehr um dieselben Methoden handelt! Es sind im Grunde zwei verschiedene Methoden, denen nur derselbe Bauplan aus der Klassendefinition zugrunde liegt.

Die erste Methode to_s gehört zum Objekt h1, die zweite zum Objekt h2. Beide Objekte haben unterschiedliche Attribute, d.h. beide Häuser haben unterschiedliche Farben. Somit liefern beide Methoden unterschiedliche Ergebnisse, obwohl sie denselben Namen haben. Eine Objektmethode arbeitet immer auf den Daten des jeweiligen Objektes.

Belassen wir es für heute dabei. Du solltest erkannt haben, wie sich OOP von dem prozeduralen Programmieren unterscheidet.

Trackbacks

Verwenden Sie den folgenden Link zur Rückverlinkung von Ihrer eigenen Seite:
http://www.rubykids.de/trackbacks?month=11&year=2007&article_id=lektion-16-objekte-na-endlich&day=17

Meine Nachricht

Einen Kommentar hinterlassen

Comments