Ruby-Programmierung: Klassen

Zurück zum Inhaltsverzeichnis.

Typischerweise erfolgt eine Einführung in objektorientierte Programmierung mittels einem Beispiels anhand dessen man die sowohl die Eigenarten der Objekte, als auch deren Umsetzung in einer bestimmten Sprache kennenlernen soll. Dieses Kapitel folgt diesem Konzept mit einer kleinen Abwandlung: Vor einem solchen Beispiel soll Objektorientierung motiviert werden, denn Sie könnten die berichtigte Frage stellen, wozu ein solches Konzept nötig ist, wenn Sie mit den in den Grundlagen beschriebenen Konzepten vollständig programmieren können.

Motivation

Bearbeiten

Diese Einleitung dient vor allem Neulingen beim Verständnis, wenn Sie bereits eine andere Objektorientierte Sprache beherrschen können Sie diesen Abschnitt überspringen.

Kehren wir einmal zurück zu dem Beispiel aus den Grundlagen mit dem Kassenbon. Dieses zugegebenermaßen nicht besonders ausgefeilte Beispiel, soll uns als Grundlage dienen. Das Produkt hatte einen Namen und einen Preis. Diese Daten korrelieren offensichtlich für ein einzelnes Produkt, aber zwischen den Produkten besteht kein Zusammenhang. Realisiert wurde dies durch die Verwendung eines Hashes. Stellen Sie sich jedoch einmal vor, dass mehr Daten zusammenhängen würden. Sie müssten dann innerhalb des Hashes wieder ein Hash oder ein Array verwenden und sich zusätzlich der Struktur ihres Datentyps an jeder Stelle im Programm bewusst sein.

Kapselung ist die erste wichtige Eigenschaft von Objekten, es bedeutet dass innerhalb eines Objektes Daten vorliegen, die über dieses objekt miteinander in Verbindung stehen. In unserem Beispiel also Name und Preis. In den meisten Umsetzungen von Objekten werden jedoch nicht nur Daten gekapselt, sondern auch Methoden. Dies hat den Vorteil, dass ein String#+ etwas völlig anderes bedeuten kann als Array#+, weil es sich um Methoden des Objektes String bzw. Array handelt.

Nun zurück zur Praxis:

In der Motivation wurde das Wort Klasse nicht erwähnt, weil es für das Konzept von Objekten nicht wichtig ist. Es gibt objektorientierte Sprachen, die völlig ohne ein Klassensystem auskommen. Klassen sind in erster Linie ein Bauplan bzw. eine Beschreibung des Objekts, während eine konkrete Umsetzung oder auch Instanz genannt ein Objekt ist.

Klassennamen müssen gemäß Sprachdefinition mit Großbuchstaben anfangen. Es ist üblich, Klassennamen im CamelCase zu schreiben. Man verzichtet auf die Verwendung von Unterstrichen und benutzt stattdessen Großbuchstaben jeweils am Anfang eines neuen Wortes oder Wortteils, z.B. Drink oder LongDrink.

Die Definition einer Klasse erfolgt mit dem Schlüsselwort class und wird wie üblich mit end beendet. Eine minimale Klasse entspricht folgendem Schema:

class KlassenName
  def initialize
  end
end

Diese Klasse hat einen Konstruktor KlassenName.new. Dieser ist dafür verantwortlich ein neues Object zu erstellen. Dafür sind zwei Schritte notwendig: Zum einen das Anfordern des Speicherplatzes vom Betriebssystem und zum anderen die spezielle Initialisierung des Objekts auf seinen Anfangszustand. Da dies immer der Fall ist, muss nicht jedesmal KlassenName.new implementiert werden, sondern diese Methode ruft ihrerseits die oben geschriebene Methode initialize auf.

Instanzmethoden und -variablen

Bearbeiten

Die oben beschriebene Klasse erfüllt nur begrenzten Sinn, da man zwar von ihr Objekte erzeugen kann, diese aber nichts können. Möchte man also Daten und Funktionalität hinzufügen, so tut man dies mit Instanzmethoden und -variablen. Diese beziehen sich auf ein konkretes Objekt, d.h. dass sich Instanzvariablen also innerhalb Objekten der selben Klasse unterscheiden können und Instanzmethoden erst nach Konstruktion eines Objektes aufgerufen werden können.

Die erste Instanzmethoden ist initialize. Sie werden ganz normal mit def eingeleitet und mit end abgeschlossen. Der einzige Unterschied zu normalen Methoden besteht also darin, dass sie in einer Klassendefinition stehen.

Instanzvariablen beginen mit einem @-Zeichen. Bedienen wir uns wieder eines Beispiels: Angenommen Sie entwickeln ein Text-RPG und wollen nun ein Schwert hinzufügen. Es hat eine Beschreibung und einen Namen und man kann damit zuhauen.

class Sword
  def initialize(name,description)
    @name = name
    @description = description
  end
	
  def hit
    puts "The mighty sword #{@name} hits!"
  end
end

Alle Instanzvariablen sind nicht von außen sichtbar, dass bedeutet im vorrangegangenen Beispiel weiß zwar das Objekt die Beschreibung des Schwertes, jedoch kann man nicht von außen darauf zugreifen. Alle Veränderungen und Ausgaben des Objektes erfolgen über Instanzmethoden! Es ist Möglich diese manuell zu erstellen indem man zum Beispiel folgende Methoden hinzufügt:

class Sword
  def name
    @name
  end
	
  def name=(name)
    @name = name
  end
end

Oder die Methoden attr_reader, attr_writer oder attr_accessor verwenden um jeweils das Lesen, Schreiben oder beides eines Symbols zu ermöglichen. Im Obigen Fall heißt das also:

class Sword
  attr_accessor :name, :description
end

Attribute von Instanzmethoden

Bearbeiten

Wie oben erläutert handelt es sich bei den Instanzvariablen grundsätzlich um private Eigenschaften des Objektes, das bedeutet das niemand direkt auf sie zugreifen kann, sondern dies durch die Verwendung von Methoden geschieht (wenn überhaupt). Auch Instanzmethoden kann man auf diese weise vor einem Zugriff schützen. Der Standard ist public, dass heißt Instanzmethoden können von überall im Programm aufgerufen werden. Dazu gibt es private, d.h. die Methode kann nur innerhalb des Objektes aufgerufen werden und protected, d.h. die Methode kann innerhalb aller Objekte der gleichen Klasse und deren Erben aufgerufen werden.

Klassenmethoden und -variablen

Bearbeiten

Klassenmethoden wie zum Beispiel File.delete können ohne die Erzeugung eines Objekts aufgerufen werden. Beim Beispiel des Files ist die Notwendigkeit offensichtlich: die Erzeugung eines Fileobjekts würde das Öffnen der Datei voraussetzen, jedoch können geöffnete Dateien nichtmehr gelöscht werden. Die häufigste Verwendung einer Klassenmethode ist der Konstruktor .new.

Für die Definition von Klassenmethoden existieren zwei mögliche Schreibweisen, die man Abhängig von der Anzahl zu definierender Klassenmethoden verwendet werden sollte. Sind wenige Methoden zu definieren, dann ist def self.method leichter zu lesen, während bei vielen Klassenmethoden die zweite Möglichkeit weniger Code wiederholt.

class Example
  def self.hello(str)
    puts "Hello #{ str }!"
  end

  class << self
    def hello2(str)
      puts "And hello #{ str }!"
    end
  end
end

Example.hello("World")
Example.hello2("Wikipedia")

Klassenvariablen beginnen mit @@. In jeder Objektinstanz der Klasse haben sie den gleichen Wert und sind daher quasi globale Variablen im Kontext einer Klasse. Aufgrund des seltsamen Verhalten von Klassenvariablen bei der Verwendung von Vererbung, sollte man Sie entweder durch globale Variablen ersetzen, oder durch Umstrukturierung des Programms überflüssig machen.