The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Klassen, Objekte, Attribute und Methoden

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Michael Neumann

Posts: 66
Nickname: backflash
Registered: May, 2003

Michael Neumann is fallen in Love with Ruby
Klassen, Objekte, Attribute und Methoden Posted: Aug 16, 2004 10:24 AM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Michael Neumann.
Original Post: Klassen, Objekte, Attribute und Methoden
Feed Title: Mike's Weblog
Feed URL: http://www.ntecs.de/blog-old/index.rss?cat=ruby&count=7
Feed Description: Blogging about Ruby and other interesting stuff.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Michael Neumann
Latest Posts From Mike's Weblog

Advertisement
In diesem Artikel versuche ich mal die im Titel genannten Begriffe zu erklären. Diese Begriffe bilden die Grundlage nicht nur der Objektorientierten Programmierung, sondern finden vielmehr auch Anwendung in anderen Disziplinen wie etwa der Biologie, und dies schon Jahrhunderte bevor die Entwicklung der Rechenmaschinen begann (ja, ich spreche von Computern ;-).

Im folgenden sind die einzelnen Begriffe erklärt:

Klasse (Schema/Schablone)

Eine Klasse beschreibt das Verhalten und die Eigenschaften all seiner Objekte. Zum Beispiel, dass jedes Haus ein Dach besitzt oder die Eigenschaft das Häuser in der Regel durch ihre Anzahl von Stockwerken beschrieben werden können.

Eine Klasse beschreibt dagegen nicht aus wievielen Stockwerken nun ein bestimmtes Haus besteht (das Nachbarhaus z.B.). Ein ganz bestimmtes Haus ist nämlich schon eine Ausprägung dieser Klasse "Haus", ein Objekt also. Man sollte hierbei gut aufpassen nicht die Klasse "Haus" mit dem Objekt "mein Haus" zu verwechseln.

Klassen sind abstrakte Beschreibungen der Objekte. Genauso gut könnte man jedes einzelne Objekt aufs neue beschreiben. Da viele Objekte sich jedoch sehr ähnlich sind ("jedes Haus hat ein Dach"), fasst man ähnliche Objekte in Klassen zusammen und beschreibt diese nur einmal.

Objekte (Ausprägung)

Objekte hingegen sind, anders als Klassen, nicht abstrakt, sie existieren wirklich! Objekte einer Klasse besitzen diesselben Verhaltensweisen (Methoden), jedoch hat jedes Objekt seine eigenen Attributwerte die es von den anderen Objekten unterscheidet. So ist z.B. jeder Mensch in Deutschland eindeutig durch die konkreten Werte von Geburtsdatum, Geburtsort usw. identifizierbar. Dies sind seine Attributwerte. Klassen beschreiben nur, welche Attribute Objekte besitzen können, nicht jedoch deren aktuelle Werte (z.B. Berlin als Geburtsort).

Attribute (Zustand)

Objekte derselben Klasse unterscheiden sich oft nur in ihren Attributwerten. Jedes Objekt hat seine eigenen Attributwerte, die jedoch mit denen eines anderen Objektes durchaus übereinstimen können. So gibt es viele Objekte der Klasse Mensch (man sagt auch "vom Typ Mensch"), die in Berlin (Attributwert des Attributs Geburtsort) geboren sind.

Ein weiteres wichtiges Attribut von Lebewesen ist der sogenannte "genetische Fingerabdruck", oder kurz, die DNS. Wieder muss unterschieden werden zwischen der Beschreibung, dass jedes Lebewesen einen genetischen Fingerabdruck besitzt (das geschieht in der Klasse), und des konkreten Wertes, der wiederum Bestandteil des zugehörigen Objektes ist.

Methoden (Verhalten)

Methoden beschreiben wie sich Objekte verhalten (können). Dabei wird dieses Verhalten für alle Objekte einer Klasse beschrieben, ist jedoch abhängig von den Attributwerten des konkreten Objektes.

Methoden beschreiben meist gewisse Aktionen. So hat zum Beispiel die Aktion geh_nach_hause eines Objektes der Klasse Mensch zur Folge, das dieser nach Hause geht. Logisch ist, das dieses Verhalten sehr stark von dem Attributwert Wohnort des Objektes abhängt. Zwei Menschen verhalten sich demnach anders, wenn der eine in Bonn und der andere in Berlin wohnt und sie aufgefordert werden nach Hause zu gehen. Anstatt von "Aufforderung" spricht man hier auch vom "Aufrufen einer Methode eines Objektes". In unserem Fall wird die Methode geh_nach_hause beider Menschen aufgerufen.

Prinzipien der Objektorientierten Programmierung

Kapselung

Was ich noch erwähnen sollte - und einige werden mir jetzt sicherlich widersprechen - ist, dass die Werte der Attribute nur dem Objekt selbst bekannt sind, solange es diese nicht preisgibt. Preisgeben kann es diese nur, wenn dafür eine spezielle Methode definiert wird. Das leuchtet ein wenn man sich einmal in Gedanken vorstellt wie die Polizei versucht die Identität einer Wasserleiche herauszufinden. Die Polizei kann ja auch nicht einfach die Werte der Attribute "Name" oder "Wohnort" abfragen. Selbst wenn die "Leiche" noch leben würde, hätte sie (die Leiche) die volle Kontrolle darüber, ob sie nun der Polizei verrät wo sie wohnt und wie sie heisst oder ob sie dies nicht tut oder gar lügt. Da eine Leiche jedoch in aller Regel tot ist (so die Definition ;-)), fällt diese Möglichkeit eh weg.

Also nochmal zum mitschreiben: Die einzigste Möglichkeit mit Objekten zu interagieren besteht darin, dessen Methoden aufzurufen, also dem Objekt mitzuteilen was wir von ihm wollen. Wir kommunizieren im Prinzip mit dem Objekt, daher sprechen wir auch häufig vom "Senden einer Nachricht" an ein Objekt anstelle von "Methodenaufruf".

Dieses Prinzip, also dass die Attributwerte nur dem Objekt bekannt sind, führt auch sofort zu einem der Grundpfeiler der Objektorientierten Programmierung (OOP), nämlich der Kapselung. Der Zustand (also die Attributwerte) eines Objektes kann ausschliesslich durch Methoden verändert bzw. abgefragt werden. Leider findet dieses ungemein wichtige Prinzip in vielen Programmiersprachen nicht die Bedeutung die es verdient. Anders in Ruby, Smalltalk, Eiffel und einigen anderen Sprachen.

Ach wie wär's schön wenn man den Zustand von Objekten direkt abfragen könnte. Das würde nämlich bedeuten, das es keine Lügen und keine Missverständnisse mehr gäbe. Aber leider gäbs dann auch keine Kommunikation mehr!

Vererbung

Ein weiterer wichtiger Aspekt der OOP neben der Kapselung ist die Vererbung. Vererbung ist sehr leicht zu erklären und zu verstehen wie ich meine. Klassen können von anderen Klassen erben. So könnten wir zum Beispiel die Klasse Katze von der Klasse Lebewesen erben lassen um auszudrücken, dass jede Katze ein Lebewesen ist und somit diesselben Eigenschaften wie ein Lebenwesen besitzt. Das spart uns ne Menge Zeit, da wir ja auch noch Hunde und Fische definieren wollen, die allesamt von der Klasse Lebenwesen erben. Das ist genauso wie in der Biologie! Natürlich können wir dann noch zusätzliche Eigenschaften für Katzen definieren und sogar die von der Klasse Lebewesen geerbten Eigenschaften durch eigene überschreiben (letzteres ist das dritte wichtige Prinzip der OOP, die Polymorphie, auf die ich aber hier nicht weiter eingehen werde).

Polymorphie

Nein, das erklär ich jetzt nicht! Polymorphie (griechisch für "vieldeutig") ist selbst ein sehr vieldeutiger Begriff. So gibt es u.a. statische und dynamische Polymorphie. Aber das interessiert uns hier nicht weiter.

Der Praktische Teil

Wir wissen nun also ungefähr was Klassen und Objekte sind und kennen auch deren Eigenschaften wie Attribute und Methoden. Nun wollen wir aber, um es besser verstehen zu können, das ganze anhand einem Beispiel vertiefen, und zwar indem wir die Tasten schwingen und gleich richtig in die Objektorientierte Programmierung (OOP) einsteigen. Und was wäre dafür besser geeignet als Ruby? Nein wirklich, schreibt diese Beispiele von mir aus in einer anderen Sprache und gebt es einem Neuling und lasst ihn entscheiden...

Und Los gehts!

Wir bauen uns ein Lebewesen... Ein Lebewesen soll ein Attribut haben, und zwar seine DNS.

  class Lebewesen
    def init(dns)
      @dns = dns
    end
  end

Wir haben also oben die Klasse Lebewesen definiert, mit einer Methode init. Diese Methode dient dazu, den Zustand des Objektes initial, also quasi gleich nach der "Geburt" zu definieren, da andernfalls das Objekt mit keinem oder nur mit einem undefinierten Zustand leben muss, was wir schliesslich nicht wollen.

In unserem Fall bezeichnet @dns das Attribut "DNS". Attribute, also die Variablen in denen der Attributwert gespeichert wird, beginnen in Ruby immer mit einem at-Zeichen (@). Damit kann man sie nicht mit lokalen Variablen verwechseln.

Wird nun die init Methode aufgerufen, so weisen wir der Zustandsvariablen @dns den Wert zu, den wir der Methode beim Aufruf als Parameter übergeben haben, hier ist das der Wert des Parameters dns.

Um nun ein neues Lebewesen Objekt zu erzeugen dessen Zustand initial leer ist, rufen wir die allocate Methode der Klasse Lebewesen auf. Das geht in Ruby, da dort Klassen in Wirklichkeit auch Objekte sind, Klassen-Objekte eben ;-). Und wen ich jetzt total verwirrt habe, der denke sich einfach eine spezielle Operation, mit der man von einer Klasse ein "frisches", d.h. leeres Objekt erzeugen kann (z.B. den new Operator aus C++ oder Konstruktoren).

  # wir erzeugen ein neues "frisches" Lebewesen
  neuesLebewesen = Lebewesen.allocate

Nun haben wir ein frisches Lebewesen erschaffen und haben es in der Variablen neuesLebewesen vorerst zwischengespeichert. Jetzt wollen wir ihm noch seine DNS zuweisen. Dazu müssen wir seinen Zustand, genauer seine Zustandsvariable @dns, ändern. Da wir aber selbstverständlich nicht direkt auf den Zustand eines Objektes zugreifen können (Gott sei Dank! Wer will schon gerne das sich seine DNS so einfach ändern lässt), müssen wir das Objekt höftlich fragen "es solle doch seine DNS auf den von uns genannten Wert setzen". Klar, dafür ist ja auch die init Methode gedacht:

  # und initialisieren seinen internen Zustand
  neuesLebewesen.init("AA-B-AC-DA")

Wenn du das allocate oben nicht verstanden hast, egal! Es ist eine vordefinierte Methode, die jedes Klassenobjekt automatisch "erbt". Lebewesen oben im Quellcode ist nämlich das Objekt das die Klasse "Lebewesen" beschreibt. Und auf Objekte können wir bekanntermassen Methoden anwenden, nicht auf Klassen, da Klassen abstrakte Dinge sind die so nicht existieren (ausser in unseren Gedanken oder in Form von Objekten die wiederrum Klassen beschreiben). Auf keinen Fall jetzt durcheinander kommen, das ist alles nicht soo wichtig!

Schön wäre es doch nun, ein Objekt bei dessen Erschaffung gleich mit seinem Zustand initialisieren zu können. Das ist sogar noch einfacher in Ruby und geht so:

  class Lebewesen
    def initialize(dns)
      @dns = dns
    end
  end

  # neues Lebewesen erzeugen und initialisieren
  neuesLebewesen = Lebewesen.new("AA-B-AC-DA")

Wieder ist new eine bereits vordefinierte Methode in Ruby, die im Prinzip allocate aufruft um ein neues Objekt zu erzeugen. Danach ruft sie die initialize Methode des neu erzeugten Objektes auf und gibt ihr all die Parameter mit die sie selbst bekommen hat. Es ist also ungefähr äquivalent zu:

  l = Lebewesen.allocate
  l.initialize("AA-B-AC-DA")

Nur funktioniert das nicht ganz, da initialize standardmässig von Ruby als private deklariert wird, d.h. sie kann nicht von ausserhalb des Objektes aufgerufen werden, ähnlich wie die Attribute nach aussen hin abgeschottet sind. Aber das nur am Rande.

Warum gerade initialize? Nun, das ist einfach Konvention. new ruft eben diese Methode auf! Aber wie wir gesehen haben, sind wir nicht gezwungen diese Methode zu definieren.

Klonen Leichtgemacht

Tja, wer hätte es geahnt, aber wir in Ruby können schon über 10 Jahre lang Lebewesen klonen, und zwar ohne das dabei Frankensteins entstehen ;-) Oder etwa doch?

  frank = Lebewesen.new('AA-BB-CC')
  frankenstein = frank.clone

  p frank.id == frankenstein.id  # => false
  p frank == frankenstein        # => true

Das p in den letzen zwei Zeilen ist eine Kurzform für print. Es stellt sich heraus, das wir zwei Objekte erzeugt haben (sie haben unterschiedliche ids), die jedoch ansonsten identisch sind.

Dieser Einschub war aber eher spasseshalber gedacht.

Es regnet Hunde und Katzen

Soweit so gut. Jetzt wollen wir uns paar kleine Kätzchen definieren. Eine Katze hat neben der DNS noch einen Namen.

  class Katze < Lebewesen
    def initialize(dns, name)
      super(dns)
      @name = name
    end

    def miau
      print "miau, ich bin " + @name
    end
  end

  punkti = Katze.new("AA-BB", "Punkti")
  flocki = Katze.new("AC-DC", "Flocki")

  punkti.miau   # => "miau, ich bin Punkti"
  flocki.miau   # => "miau, ich bin Flocki"

Neu ist hier die Verwendung von super. Dies ruft die Methode der Superklasse, in diesem Fall also initialize der Klasse Lebewesen auf. Klar sollte sein, dass die Klasse Katze von der Klasse Lebewesen erbt.

Zusätzlich wollen wir jetzt aber den Namen der Katze abfragen können. Wir definieren also eine Methode name, die ganz einfach das Attribut @name zurückliefert.

  class Katze
    def name
      @name
    end
  end

Zu beachten gilt, dass in Ruby der letzte Wert einer Methode als Rückgabewert zurückgegeben wird, @name also im Beispiel oben.

Jetzt brauchen wir noch Hunde. Hunde haben der Einfachheit halber keinen Namen (och, bin ich fies ;-). Dafür können sie bellen und man kann sie Katzen jagen lassen.

  class Hund < Lebewesen
    def wau
      print "wau wau"
    end

    def jage(katze)
      print "ich jage " + katze.name
    end
  end

Wir müssen auch gar keine initialize Method definieren da diese aus der Klasse Lebewesen geerbt wird.

Dann lasst uns mal spielen ;-)

  bello = Hund.new("AA-ZZ")   # auch Hunde haben eine DNS

  punkti.miau                 # => "miau, ich bin Punkti"
  bello.wau                   # => "wau wau"
  bello.jage(punkti)          # => "ich jage Punkti"

Und was passiert wenn wir eine Katze bellen lassen? Einfach ausprobieren:

  punkti.wau
  # => NoMethodError: undefined method `wau' for #<Katze:0x81e9748>

Da Ruby weiss dass punkti eine Katze ist, und Katzen keine Methode wau definieren, teilt es uns mittels einer Ausnahme (Exception) mit, dass diese Methode nicht existiert. Genauso gut können wir einen Hund einen anderen Hund jagen lassen, oder ein beliebiges anderes Objekt. Da ein Hund jedoch keine Methode name hat, wird Ruby uns wieder eine Ausnahme melden. Das heisst also, dass wir die Methode jage mit einem Objekt als Parameter aufrufen müssen, für das die Methode name definiert ist. Diese Vorgehensweise nennt man auch liebevoll Duck Typing. Dave Thomas hat diesen Begriff geprägt, wenn ich mich recht erinnere.

  "Everything that walks like a duck, quacks like a duck, is a duck"

Gemeint damit ist, dass wir nicht überprüfen ob ein Objekt der Klasse Ente angehört wie dies in Java, C++ und vielen anderen Sprachen der Fall ist (das nennt man übrigens Tag-typing), sondern einfach nur testen ob dieses Objekt die Methoden watschel und quack definiert. Wenn ja, dann handelt es sich für uns um eine Ente, ansonsten nicht.

Read: Klassen, Objekte, Attribute und Methoden

Topic: Seeing is believing &mdash; even for programmers! Previous Topic   Next Topic Topic: Rails attempts a ride to RubyConf

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use