Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

 << zurück
Praxisbuch Objektorientierung von Bernhard Lahres, Gregor Raýman
Professionelle Entwurfsverfahren
Buch: Praxisbuch Objektorientierung

Praxisbuch Objektorientierung
609 S., 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-624-6
gp Kapitel A Anhang
  gp A.1 Verwendete Programmiersprachen
    gp A.1.1 C++
    gp A.1.2 Java
    gp A.1.3 C#
    gp A.1.4 JavaScript
    gp A.1.5 CLOS
    gp A.1.6 Python
    gp A.1.7 Ruby

Wir haben in diesem Buch in Beispielen eine ganze Reihe von Programmiersprachen verwendet. In diesem Anhang geben wir zu jeder dieser Sprachen eine sehr kurze Beschreibung und stellen deren grundlegende Eigenschaften mit Bezug zur Objektorientierung vor. Außerdem geben wir Verweise auf weitere Informationen zu diesen Sprachen und womöglich auf freie Software, mit der Compiler, Interpreter oder komplette Entwicklungsumgebungen für diese Sprachen umgesetzt wurden.

Kapitel A Anhang


Rheinwerk Computing

A.1 Verwendete Programmiersprachen  downtop

Wir stellen die verwendeten Programmiersprachen hier sehr kurz mit ihrem Steckbrief vor. Dabei geben wir nur einen ganz knappen Abriss. Jede Sprache wird mit ihren Basiskonstrukten, die relevant für die objektorientierten Eigenschaften sind, vorgestellt.


Rheinwerk Computing

A.1.1 C++  downtop

C++ wurde von Bjarne Stroustrup als objektorientierte Erweiterung zur Programmiersprache C entwickelt. Bis Ende der 90er-Jahre war C++ die meistverwendete objektorientierte Programmiersprache. Danach wurde C++ in dieser Position von Java abgelöst.

Die Struktur von C++

Klassen werden in C++ mit dem Schlüsselwort class eingeführt. Alternativ kann auch das Schlüsselwort struct verwendet werden, dies ist aber für Klassen unüblich. class und struct unterscheiden sich nur in der Sichtbarkeitsstufe, die als Voreinstellung gilt: Bei Verwendung von class ist diese private, bei Verwendung von struct ist diese public.

C++ unterscheidet nicht zwischen reiner Vererbung von Schnittstellen und der Vererbung von Schnittstelle und Implementierung. Durch die Angabe einer Klasse nach dem Klassennamen wird eine Vererbungsbeziehung etabliert. C++ unterstützt die Mehrfachvererbung von Schnittstellen und von implementierenden Klassen. Bei der Deklaration einer Klasse kann eine Liste von Klassen aufgeführt werden, die als Basisklassen agieren.

Operationen werden darüber eingeführt, dass sie zusammen mit der Klasse deklariert werden. Wenn Operationen polymorph agieren sollen, muss das bei der ersten Deklaration einer Methode über das Schlüsselwort virtual erfolgen.

Ist nichts anderes angegeben, gilt für alle Elemente, die innerhalb einer Klasse (über class deklariert) deklariert werden, die Sichtbarkeitsstufe private. Neben den weiteren Sichtbarkeitsstufen protected und private kennt C++ auch die Beziehung friend zwischen zwei Klassen. Wenn Klasse B als friend von A definiert ist, können Exemplare von B auf private Datenelemente und Methoden von A zugreifen.

Die Syntax von C++

Wir stellen die Syntax von C++ an einem einfachen Beispiel vor. Die Klasse VersionNumber erbt dabei von einer Klasse StructuredName und implementiert eine Schnittstelle, die von der Klasse Comparable vorgegeben wird.

class StructuredName 1
      { /* ... */ };
      
      class Comparable 2
      {
          public:
            virtual int compareTo(const Comparable& other) = 0; 3
      };
      
      class VersionNumber : public StructuredName, Comparable 4
      {
private:
          list<string> parts;    5
      
      public:
          VersionNumber(list<string> parts) 6
          {
              this->parts = parts;
          }
          virtual ~VersionNumber() {};
          virtual string toString();  7
          virtual int compareTo(const Comparable& other);
      
       };
string VersionNumber::toString()  8
      {
          string result = "";
          list<string>::const_iterator iter;  9
          for (iter=parts.begin(); iter!=parts.end();iter++)
          {
              if (iter != parts.begin())
                  result += ".";
              result += *iter;
          }
          return result;
      }
 
int main()
      {
          list<string> parts;
          parts.push_back("6");
          parts.push_back("0");
          parts.push_back("2800");
          parts.push_back("1106");
          VersionNumber v(parts); J
          VersionNumber *p = new VersionNumber(parts);
          cout << v.toString();
          cout << p->toString();
      }

1 Wir deklarieren eine Klasse StructuredName.

2 Wir deklarieren eine abstrakte Klasse Comparable.

3 Diese spezifiziert eine Operation compareTo, die über den Ausdruck = 0 als abstrakt markiert wird.

4 Die Klasse VersionNumber erbt von beiden genannten Klassen.

5 Die Instanzvariable parts enthält eine Liste von Strings.

6 Bei der Konstruktion wird eine Liste von Strings übergeben, die der Instanzvariablen parts zugewiesen wird.

7 Die Operation toString wird mit dem Schlüsselwort virtual markiert. Damit unterliegt sie der Polymorphie und kann in abgeleiteten Klassen überschrieben werden.

8 Da die Umsetzung der Methode toString für die Klasse VersionNumber außerhalb der Klassendeklaration erfolgt, muss der durch die Klasse vorgegebene Namensraum als VersionNumber:: der Methodenimplementierung vorangestellt werden.

9 Über einen Iterator geht das Programm alle Elemente der Liste parts durch.

J Hier sehen wir zwei unterschiedliche Möglichkeiten der Objektkonstruktion. Zum einen haben wir die lokale Variable v vom Typ VersionNumber mit einem Exemplar von VersionNumber initialisiert. Zum anderen haben wir der Variablen p einen Zeiger auf ein Exemplar von VersionNumber zugewiesen, wobei das Objekt auf dem Heap angelegt wurde. Mit dem Operator -> kann eine Operation auf dem Objekt aufgerufen werden, auf das ein Zeiger verweist.

Ressourcen

Der am weitesten verbreitete freie Compiler für C++ ist derjenige, der als Teil des GNU-Projekts unter http://gcc.gnu.org/ zur Verfügung steht. Der Compiler steht für eine breite Palette an Plattformen zur Verfügung. Eine Integration in die Eclipse-Entwicklungsumgebung ist über das CDT-Projekt (C/C++ Development Tools) verfügbar. CDT integriert den GNU-C++-Compiler (oder auch andere Compiler) in die Eclipse-Umgebung. CDT ist verfügbar unter http://www.eclipse.org/cdt/.


Rheinwerk Computing

A.1.2 Java  downtop

Java ist eine statische und stark typisierte klassenbasierte Programmiersprache, welche die einfache Klassifizierung und die einfache Vererbung unterstützt.

Java wurde Mitte der Neunzigerjahre von Sun entwickelt. Im Gegensatz zu C++ werden Java-Programme nicht direkt in den Maschinencode der jeweiligen Zielplattform übersetzt, sondern in einen Bytecode, der von der Java Virtual Machine interpretiert wird. Auf diese Weise kann man übersetzte Java-Klassen auf jeder unterstützten Plattform verwenden. In Java gibt es keine Zeiger, und alle Objekte werden dynamisch auf dem Heap angelegt. Die Speicherverwaltung ist in Java automatisch, das heißt, dass das Löschen von Objekten nicht explizit programmiert werden muss, sondern von einem Garbage Collector erledigt wird.

Struktur von Java

In Java gibt es einerseits unstrukturierte primitive Datentypen wie char, int, boolean, long und so weiter und andererseits Klassen. Java kennt zwei Arten von Klassen: Die Schnittstellenklassen werden mit dem Schlüsselwort interface deklariert und können nur abstrakte objektbasierte Operationen deklarieren, sie aber nicht implementieren. Implementierende Klassen werden mit dem Schlüsselwort class deklariert und können Datenelemente und Methodenimplementierungen definieren. Sowohl die implementierenden Klassen als auch die Schnittstellen können »statische« Datenelemente und Methodenimplementierungen enthalten.

Java unterstützt nur die einfache Vererbung der implementierenden Klassen, die Mehrfachvererbung funktioniert nur für die Schnittstellen. Eine implementierende Klasse kann also nur von einer anderen implementierenden Klasse erben, sie kann aber mehrere Schnittstellen implementieren. Eine Schnittstelle kann von mehreren Schnittstellen erben. Die gemeinsame Oberklasse aller implementierenden Klassen ist die Klasse Object.

Alle Variablen und alle Routinen sind in Java Klassen zugeordnet. Es gibt in Java keine globalen Variablen.

Syntax von Java

Die Syntax von Java kann man an folgendem Beispielquelltext vorstellen:

public class VersionNumber 1
         extends NamePart 2 implements Comparable 3{
        private final int[] parts 4;
        public VersionNumber(int[] parts) 5 {
          this.parts = parts.clone() 6;
        }
        public String toString() 7 {
          StringBuilder builder = new StringBuilder() 8;
          String separator = "";
          for (int part: parts) { 9
            builder.append(separator);
            builder.append(part);
            separator = ".";
          }
          return builder.toString();
        }
      
        public int compareTo(Object o) {
          ... // Implementierung J
        }
        /* Weitere Methoden und Datenelemente
           folgen hier */
      }

1 Der Name der Klasse ist VersionNumber.

2 Sie erbt von der Klasse NamePart

3 und implementiert die Schnittstelle Comparable.

4 Jedes Exemplar dieser Klase enthält eine nicht änderbare (final) private Variable mit dem Namen parts, deren Datentyp ein Array von Integern ist.

5 Die Exemplare werden mit einem Konstruktor initialisiert, der als Parameter eine Variable mit dem Namen parts, deren Datentyp ein Array von Integern ist, initialisiert.

6 Die Objektvariable parts wird von dem Parameter parts durch das Schlüsselwort this unterschieden. Sie wird mit einer Kopie des Arrays, das von dem Parameter parts referenziert wird, initialisiert.

7 Die Klasse implementiert eine öffentliche Methode toString, deren Rückgabewert den Datentyp String hat.

8 In dieser Methode wird eine lokale Variable mit dem Namen builder deklariert und mit einem neuen Exemplar der Klasse StringBuilder initialisiert.

9 In einer Schleife geht das Programm alle Elemente des Arrays parts durch. Hier braucht man kein this, da es keine lokale Variable und keinen Parameter mit dem Namen parts gibt.

J Einzeilige Kommentare werden in Java mit //, mehrzeilige mit /**/ markiert.

Ressourcen

Der zentrale Einstiegspunkt zur Sprache Java ist bei Sun unter http://java.sun.com zu finden. Dort finden sich die verschiedenen Java-Technologien versammelt. Die gängigsten davon sind die Java 2 Standard Edition (J2SE) und die Java 2 Enterprise Edition (J2EE). Von den in großer Zahl existierenden Angeboten mit Ressourcen und Informationen zu Java wollen wir hier nur das Angebot von jguru herausheben, das unter http://www.jguru.com zu finden ist.

Einen sehr guten Überblick über die Sprache bietet das Handbuch Java ist auch eine Insel von Christian Ullenboom, das unter https://www.galileo-press.de/openbook/javainsel5/ als Open Book online verfügbar ist.


Rheinwerk Computing

A.1.3 C#  downtop

C# ist das programmiersprachliche Flaggschiff der .Net-Sprachflotte. .NET ist ein von Microsoft entwickeltes Konkurrenzprodukt zu der Java-Welt. .Net unterstützt mehrere Programmiersprachen, C# und Visual Basic.NET sind die prominentesten Vertreter der .NET-Sprachen.

Alle diese Programmiersprachen werden von ihren Compilern in eine Intermediate Language (IL) übersetzt, und zur Laufzeit von einem Just in Time Compiler in die Maschinensprache der Zielplattform übersetzt.

C# ist ähnlich wie Java eine statisch stark typisierte, klassenbasierte Programmiersprache mit einfacher Klassifikation der Objekte. Ähnlich wie in Java gibt es in C# keine Mehrfachvererbung der Implementierung, daher unterscheidet C# zwischen den Schnittstellen (interface) und den implementierenden Klassen. C# unterscheidet zwischen den Referenztypen (class) und den Wertetypen. Die Exemplare der Referenztypen werden immer einzeln auf dem Heap angelegt, die Exemplare der Wertetypen können auch auf dem Stack angelegt werden und als Teile anderer Strukturen, wie zum Beispiel Arrays, existieren. Zu den Wertetypen gehören die primitiven Typen wie int, char oder bool und die strukturierten Typen (struct). Die Strukturen können genauso wie die Klassen eigene Datenelemente und Methoden haben und Schnittstellen implementieren. Sie können aber nicht von anderen implementierenden Klassen erben; sie selbst können keine Oberklasse sein.

C# hat gegenüber Java manche Erweiterungen, wie zum Beispiel die Delegaten und die Eigenschaften der Objekte. Die Syntax von C# ist der Syntax von Java sehr ähnlich, so dass wir hier auf eine Vorstellung der Syntax von C# verzichten, da wir davon ausgehen, dass die Beispiele in diesem Buch trotzdem verständlich sind.


Rheinwerk Computing

A.1.4 JavaScript  downtop

JavaScript wurde ursprünglich bei Netscape entwickelt und hatte seinen ersten Einsatz in der Version 2 des Netscape Navigators. JavaScript wurde dort eingesetzt, um Webseiten mit dynamisch erzeugten Inhalten zu versehen. Microsoft entwickelte eine Entsprechung hierzu unter dem Namen JScript. In den Versionen des Internet Explorers ab Version 3.0 wird also nicht eigentlich JavaScript unterstützt, sondern JScript. Allerdings erfolgte eine Vereinheitlichung der beiden damit existierenden De-facto-Standards unter dem Namen ECMA-Script. ECMA stand ursprünglich für European Computer Manufacturers Association, mittlerweile gibt es diese aber nicht mehr unter diesem Namen, und die ECMA ist zu einer Institution geworden, die Standards im Bereich der IT-Technologie verwaltet. Wenn das verwirrend klingt, ist das kein Zufall. Es ist nämlich verwirrend. Zwar basieren die aktuellen Versionen von JavaScript und JScript beide auf der Version ECMA-262, Revision 3. Die beiden Sprachen bringen jedoch jeweils eigene Erweiterungen mit ein, so dass für eine Variante erstellte Skripte nicht unbedingt in einem anderen Browser lauffähig sind.

Allerdings hat sich als Name weder JScript noch ECMAScript durchgesetzt. Und man muss auch zugeben, dass ECMAScript nun wirklich kein schöner Name ist. Deshalb wird in der Regel einfach der Name JavaScript verwendet, auch wenn dabei eigentlich der Standard (ECMAScript) oder die Umsetzung des Standards in den Microsoft-Produkten (JScript) gemeint ist. Mit Java hat JavaScript außer der Namensähnlichkeit sehr wenig gemeinsam. Die Syntax basiert eher locker auf der Sprache C, die objektorientierten Mechanismen von JavaScript sind völlig andere als die von Java.

Struktur von JavaScript

JavaScript ist dynamisch typisiert und wird in der Regel in der jeweiligen Laufzeitumgebung (häufig ein Webbrowser) interpretiert.

JavaScript kennt keine Klassen. Eine Art von Vererbungsbeziehung ist dennoch über die Zuordnung von so genannten Prototypen möglich. Jedem Objekt kann dadurch ein Prototyp (wieder ein Objekt) zugeordnet werden, dessen Eigenschaften das Objekt mit übernimmt. Dadurch können Ketten aufgebaut werden, die eine Art von Vererbungsbeziehung darstellen. Jedes Objekt erbt damit alle Eigenschaften der folgenden Objekte in der Kette.

Da diese Beziehungen zwischen Objekten bestehen und nicht zwischen Klassen, können die Struktur und die Eigenschaften aller Objekte zur Laufzeit angepasst werden. Wird also ein Objekt zur Laufzeit geändert, das als Prototyp für mehrere andere Objekte agiert, ändern sich auch die Eigenschaften dieser beteiligten Objekte.

Syntax von JavaScript

function StructuredName()
      {
          // ... Funktionalitäten von strukturierten Namen
      }
      
      function VersionNumber(parts) 1
      {
          this.parts = parts; 2
          this.toString = function() 3
          {
              description = "";
              for (var i = 0; i < parts.length – 1; ++i) 4
              {
                 description = description + parts[i] + ".";
              }
              return description + parts[parts.length – 1]; 5
      
          };
      }
      
      VersionNumber.prototype = new StructuredName(); 6
      
      var parts = new Array("6","0","2800","1106"); 7
      var version = new VersionNumber(parts); 8
      alert(version.toString()); 9
      version.product = "Internet Explorer"; J

1 Die Funktion VersionNumber, die als Vorlage für Objekte dient.

2 Jedes über die Funktion VersionNumber erstellte Objekt enthält eine Variable mit dem Namen parts. Da JavaScript dynamisch typisiert ist, ist der Typ der Variablen nicht festgelegt. Da die Variable über this.parts deklariert ist, ist sie auch nach außen sichtbar. Wenn wir stattdessen die Deklaration var myparts = parts verwendet hätten, wäre die Sichtbarkeit auf interne Methoden beschränkt. In JavaScript lassen sich also durchaus Entsprechungen zu privaten Instanzvariablen realisieren.

3 Jedes über die Funktion erstellte Objekt hat wiederum eine Methode toString, die an dieser Stelle implementiert wird.

4 An dieser Stelle wird die Annahme gemacht, dass es sich bei parts um ein Array handelt, das hier durchlaufen wird. Sollte parts zur Laufzeit mit einem Objekt eines anderen Typs belegt sein, kommt es zu einem Laufzeitfehler.

5 Die Methode toString gibt ihr Ergebnis über return zurück.

6 Über die Zuweisung VersionNumber.prototype = new StructuredName() wird eine Vererbungsbeziehung etabliert. Die über die Funktion VersionNumber erstellten Objekte haben alle Eigenschaften, die das über new StructuredName() erstellte Objekt aufweist.

7 Wir legen ein Objekt vom Typ Array an und weisen dieses der Variablen parts zu.

8 Über den Aufruf von new in Kombination mit einer Funktion wird die Funktion als Objektkonstruktor benutzt. Es wird ein neues Objekt erstellt und mit den in der Funktion definierten Eigenschaften ausgestattet.

9 Die Methode toString wird für das neu angelegte Objekt aufgerufen.

J Dem Objekt, das der Variablen version zugewiesen ist, ordnen wir nun ein neues Attribut product zu und belegen es mit dem Wert »Internet Explorer«. Damit unterscheidet sich dieses Objekt in seiner Struktur von anderen Objekten, die über die Funktion VersionNumber angelegt wurden.

Ressourcen

Eine online verfügbare gute Dokumentation zur JavaScript ist Teil der von Stefan Münz gepflegten Selfhtml-Webseite. Unter http://de.selfhtml.org/javascript/ findet sich eine gute Übersicht über die Sprache und auch ihre Geschichte.


Rheinwerk Computing

A.1.5 CLOS  downtop

CLOS steht für Common Lisp Object System und wird meistens als C-LOS (und nicht KLOS) ausgesprochen. CLOS ist eine objektorientierte Erweiterung zu Common Lisp, einer im Kern funktionalen Programmiersprache. Ähnlich wie C++ als Zusatz zu C entwickelt wurde, übernimmt CLOS alle Sprachkonstrukte von Common Lisp und stellt zusätzliche Möglichkeiten für objektorientierte Programmierung zur Verfügung.

In der Praxis ist die Bedeutung von CLOS eher gering, die Sprache wurde vor allem im Universitäts- und Forschungsbereich angewendet. Allerdings ist in CLOS eine Reihe von innovativen Konzepten enthalten, die in andere Sprachen eingeflossen sind. Auch die Kombination mit der dynamisch typisierten Sprache Common Lisp bietet interessante Möglichkeiten.

Ergänzt wird CLOS in vielen Implementierungen durch eine Umsetzung eines Metaobjekt-Protokolls (MOP). Dieses erlaubt Anpassungen der Sprache, um diese an unterschiedliche Anforderungen anzupassen.

Struktur von CLOS

Da CLOS auf Common Lisp basiert, ist es wie dieses dynamisch typisiert.

Operationen werden über so genannte generische Funktionen (Generic Functions) definiert. Eine Methode implementiert eine generische Funktion für eine bestimmte Menge von Parametertypen. Methoden sind also nicht genau einer Klasse zugeordnet. Vielmehr wird beim Aufruf einer generischen Funktion anhand der Werte aller übergebenen Parameter bestimmt, welche Methode verwendet wird. Da Methoden damit nicht klassengebunden sind, ist eine Erweiterung um neue Operationen und Methoden also möglich, ohne den Source-Code von existierenden Klassen anpassen zu müssen.

Mehrfachvererbung von implementierenden Klassen ist in CLOS möglich. Dabei erben die abgeleiteten Klassen die Datenelemente der Basisklassen. Wir können allerdings nicht direkt davon sprechen, dass auch die Methoden der Basisklasse erben, da diese ja nicht einer einzigen Klasse zugeordnet werden müssen.

Syntax von CLOS

(defclass NamePart
         (...))
      (defclass VersionNumber 1 (NamePart) 2
          ((parts :initform nil 3
                  :initarg :parts
                  :accessor parts)))
      
      (defgeneric toString (printable))  4
      (defgeneric compareTo (comparable))
      
      (defmethod toString ((printable VersionNumber)) 5
        (let ((string ""))  6
          (dolist (item (parts printable)) 7
            (if (equal string "")
              (setf string (format nil "~A" item))
              (setf string (format nil "~A.~A" string
                           (format nil "~A" item)))))
          string)) 8
      
      (defmethod compareTo ((comparable VersionNumber))
        ;; Hier erfolgt die Implementierung von compareTo
      )
      
      (defun ausgabe() 9
        (let* ((num (make-instance 'VersionNumber J
                                   :parts '(6 0 2800 1106))))
              (toString num)))
      
      [2]> (ausgabe)
      "6.0.2800.1106"
      [3]>

1 Wir deklarieren eine Klasse VersionNumber.

2 Sie erbt von der Klasse NamePart.

3 Jedes Exemplar dieser Klasse enthält eine Variable mit dem Namen parts. Der Datentyp ist nicht festgelegt. Die Angabe von initform legt fest, dass diese Variable standardmäßig mit dem Wert nil vorbelegt wird. Die Angabe von :initarg legt fest, dass bei der Konstruktion auch ein anderer Wert angegeben werden kann. Der Name des betreffenden Parameters ist dann :parts. Der Zugriff auf die Variable erfolgt über den so genannten Accessor, der ebenfalls parts heißt.

4 toString und compareTo werden als Operationen (generische Funktionen) deklariert. Dadurch können Methoden zur Umsetzung der Operationen implementiert werden, deren Aufruf über den Mechanismus der Polymorphie gesteuert wird.

5 Die Methode toString implementiert die Operation toString für Exemplare der Klasse VersionNumber.

6 Über (let ((string "")) wird eine lokale Variable string deklariert und mit dem Leerstring vorbelegt. Der Sichtbarkeitsbereich ist durch die umgebenden Klammern festgelegt.

7 dolist führt eine angegebene Funktion für alle Elemente einer Liste aus. In diesem Fall erfolgt eine Ausgabe des Listenelements item für alle Elemente der Liste, die über den Accessor-Aufruf (parts printable) geliefert wird.

8 Die Methode toString liefert den Wert der lokalen Variablen string als Ergebnis zurück.

9 Über defun wird eine Funktion definiert, in diesem Fall eine einfache Funktion, die eine Versionsnummer konstruiert und ausgibt.

J Über make-instance wird ein Exemplar von VersionNumber konstruiert, mit den Bestandteilen für eine Versionsnummer initialisiert und der Variablen num zugewiesen. Über den Zugriff mit toString wird das Exemplar dann als String formatiert und dieser String als Ergebnis der Funktion zurückgegeben.

Ressourcen

Eine freie Implementierung von Common Lisp ist das unter http://clisp.cons.org/ verfügbare CLISP. CLISP ist für eine Reihe von Plattformen, darunter auch Microsoft Windows, verfügbar.

Eine sehr gute Übersicht über Common Lisp bietet Common LISP: The Language von Guy L. Steele [Steele 1990]. Eine Online-Version ist verfügbar unter http://www.supelec.fr/docs/cltl/clm/clm.html. Eine Beschreibung von CLOS und dem Metaobjekt-Protokoll findet sich in The Art of the Metaobject Protocol von Gregor Kiczales, Jim des Rivières und Daniel Bobrow. [Kiczales 1991]


Rheinwerk Computing

A.1.6 Python  downtop

Python ist eine interpretierte, interaktive, dynamisch typisierte objektorientierte Programmiersprache, die nach der populären britischen Komikertruppe Monty Python benannt wurde. Ihre Entwicklung startete 1990 Guido van Rossum. Es handelt sich um eine objektorientierte Programmiersprache, in der alle im Programm verwalteten Daten Objekte sind. Sogar die einfachen Werte wie die Zahlen sind Objekte mit eigenen Methoden. So entspricht der Aufruf abs(-1) dem Aufruf der Methode __abs__() der Zahl –1.

Dennoch kann man Python nicht als rein objektorientiert bezeichnen, weil Python globale Funktionen und Prozeduren zulässt und keine »echte« Datenkapselung zulässt.

Syntax von Python

Wenn man die Syntax von Python zum ersten Mal kennen lernt, kann man zuerst den Eindruck gewinnen, sich tatsächlich in einem Sketch von Monty Python zu befinden. Denn in Python, im Gegensatz zu fast allen anderen Programmiersprachen, spielt die Einrücktiefe eine wesentliche Rolle.

Die Blöcke werden durch die Anzahl der Leerzeichen am Anfang der Zeilen bestimmt. Keine Klammern, kein begin/end sind notwendig. Doch wenn man sich an die Philosophie von Python gewöhnt hat, lernt man diese Art der Blockbildung zu schätzen. In Sprachen wie C++, Java und vielen anderen, in denen die Blöcke mit Klammern oder Schlüsselwörtern bestimmt werden und die Einrücktiefe keine syntaktische Bedeutung für den Compiler oder den Interpreter hat, nutzt man die Einrücktiefe ohnehin, um die Quelltexte für den Menschen leichter lesbar zu machen.

Die Einrücktiefe und die Blocktiefe sind in solchen Programmiersprachen formell voneinander unabhängig, tatsächlich aber erwartet man, dass sie sich entsprechen und redundant dieselbe Information über die Programmstruktur tragen. Wenn sich in einem C++-Quelltext die Einrücktiefe und die durch die Klammern bestimmte Blocktiefe widersprechen, betrachtet man den Quelltext als falsch formatiert und möglicherweise fehlerhaft. Diese Redundanz und potenzielle Verwirrungsquelle gibt es in Python nicht.

Schauen wir uns jetzt die pythonsche Syntax an einem Beispielquelltext an:

class VersionNumber(object): 1
         def __init__(self, values): 2
            self.parts = values[:] 3
         def __str__(self): 4
            result = "" 5
            sep = ""
            for n in self.parts: 6
               result += sep + str(n) 7
               sep = "."
            return result 8
      
      v = VersionNumber( [1,2,3] ) 9
      print v.__str__() J

1 Hier deklarieren wir die Klasse VersionNumber, die von der Klasse object abgeleitet ist.

2 Die spezielle Methode __init__ wird beim Erzeugen eines Exemplars einer Klasse aufgerufen. Beachten Sie, dass das erste Argument self explizit genannt ist. In Sprachen wie Java oder C++ haben objektbezogene Methoden ein implizites Argument this, in Python muss dieses Argument explizit benannt werden. Den Namen self haben wir willkürlich gewählt, wir könnten diesem Argument auch einen anderen Namen geben.

3 Wir ordnen hier einen Datensatz mit dem Namen parts dem neu erzeugten Objekt self zu. Diesem Datensatz weisen wir eine Kopie der Liste zu, die als Parameter mit dem Namen values übergeben wurde.

4 Hier definieren wir die Methode __str__, die von der globalen Funktion str() aufgerufen wird. Diese wird verwendet, um eine Textrepräsentation eines Objekten zu erstellen.

5 Dazu deklarieren und initialisieren wir zuerst eine lokale Variable result

6 und zählen alle Elemente der Liste self.parts.

7 Dem result fügen wir nach und nach die Elemente getrennt durch Punkte zu.

8 Und schließlich geben wir die Variable result zurück.

9 Ein neues Exemplar der Klasse VersionNumber erzeugen wir, indem wir den Klassennamen »aufrufen«, als ob er eine Funktion bezeichnen würde. Beachten Sie bitte, dass wir nur ein Argument (die Liste [1,2,3]) übergeben, obwohl die Funktion __init__ zwei Argumente erwartet.

J Die Funktion __str__ wird hier ganz ohne Argumente aufgerufen. Python übergibt als erstes Argument automatisch das aufgerufene Objekt, in diesem Falle v.

Ressourcen

Die meistverwendete Version von Python läuft als eine native Anwendung auf verschiedenen Betriebssystemen. Die aktuelle Version ist auf der Internetseite http://www.python.org zusammen mit der Dokumentation und verschiedenen Bibliotheken verfügbar. Außerdem gibt es Implementierungen für die Java Virtual Machine (http://www.jython.org) und für das .NET-Framework (http://www.ironpython.com/).


Rheinwerk Computing

A.1.7 Ruby  toptop

Ruby ist ähnlich wie Python eine interpretierte, dynamisch typisierte objektorientierte Skriptsprache. Sie wurde von Yukihiro Matsumoto 1993 erfunden, der mit Python und dessen Objektorientierung nicht ganz zufrieden war. Der Name Ruby wurde von der Skriptsprache Perl inspiriert – es ist der Name eines Edelsteins.

Ruby kann man tatsächlich als eine rein objektorientierte Programmiersprache betrachten. Sie kennt keine globalen Routinen, jede Funktion und jede Prozedur ist eine Methode einer Klasse oder eines Objekts.

In Ruby kann man bereits bestehende Klassen und auch die Klasse object um neue Methode erweitern, die dann jedem neuen und jedem bereits existierenden Objekt zur Verfügung stehen. Dies ist den globalen Methoden recht ähnlich, unterscheidet sich von ihnen aber dennoch, denn die Methoden haben Zugriff auf die Datenelemente des konkreten aufgerufenen Objekts.

Syntax von Ruby

In Gegensatz zu Python ist die Einrücktiefe in Ruby nicht relevant. Die Gruppen von Anweisungen werden meistens durch das Schlüsselwort end geschlossen.

Eines der wichtigsten Merkmale von Ruby ist die Verwendung von Blöcken. Ein Block ist eine anonyme Methode, die in anderen Methoden deklariert werden kann. Ein Block kann entweder durch das Schlüsselwortpaar do/end markiert werden oder durch ein Paar der geschweiften Klammern { und }.

Doch auch in Ruby kann man sich so manche Klammer sparen. Hier sind nämlich die Klammern bei einem Funktionsaufruf optional. So kann man Math.sin(1.0) auch als Math.sin 1.0 schreiben. Die Klammern sind nur dann notwendig, wenn sonst nicht eindeutig wäre, wo die Liste der Parameter aufhört.

Schauen wir uns die Implementierung einer Versionsnummer in Ruby an:

class VersionNumber < Object 1
          def initialize(parts) 2
              @parts = parts.clone 3
          end
      end
      
      v = VersionNumber.new [1, 2, 3] 4

1 Hier deklarieren wir die Klasse VersionNumber, die von der Klasse Object abgeleitet wird. Die Vererbung von der Klasse Object müssten wir nicht explizit angeben, wir machen es nur, um die Syntax der Vererbungsbeziehung in Ruby zu demonstrieren. Die Namen der Klassen in Ruby fangen immer mit einem Großbuchstaben an.

2 Die Methode initialize wird beim Erzeugen eines neuen Exemplars der Klasse aufgerufen. Sie erwartet einen Parameter mit dem Namen parts.

3 Die Datenelemente eines Objektes werden in Ruby mit dem Präfix @ markiert. So kann man Parameter und lokale Variablen von den Datenelementen der Objekte unterscheiden. Die Datenelemente der Objekte sind immer privat. Mit dem doppelten Präfix @@ werden klassenbezogene Datenelemente bezeichnet.

4 Hier erstellen wir ein neues Exemplar unserer Klasse.

Moment mal, haben wir nicht etwas vergessen? Unsere Klasse braucht doch noch die Methode, die ihre Exemplare als eine Zeichenkette ausgeben kann. Kein Problem für Ruby, wir können die Klasse einfach um eine neue Methode erweitern:

class VersionNumber 5
        def to_s 6
          result = ''; sep = '' 7
          @parts.each do |element| 8
            result += sep + element.to_s
            sep = '.'
          end
          return result 9
        end
      end
      
      print v # gibt 1.2.3 aus

5 Wir deklarieren hier keine neue Klasse VersionNumber, wir wechseln lediglich den Kontext, um die neu definierten Methoden der bereits bestehenden Klasse VersionNumber zuzuweisen.

6 Wir überschreiben die geerbte Methode to_s. Wir müssen keine Klammern eingeben, weil die Methode keine Parameter erwartet.

7 Wenn man mehrere Statements auf derselben Zeile angibt, muss man sie durch ein Semikolon trennen.

8 each ist hier kein Schlüsselwort, es ist eine Methode der Liste @parts, die als Parameter einen Block erwartet. Der Block, eine anonyme Methode, wird in das Paar do/end eingeschlossen und erwartet einen Parameter, den wir hier element nennen. Die Methode each ruft den Block nacheinander für alle Elemente der Liste auf.

9 Hier verlassen wir die Methode to_s und geben den Inhalt der lokalen Variablen result zurück. An dieser Stelle ist das Schlüsselwort return nicht wirklich notwendig. Eine Methode gibt nämlich immer den letzten in ihr ausgewerteten Ausdruck zurück. Hier würde also der Text result reichen. Das Schlüsselwort return braucht man nur dann, wenn man eine Methode vor ihrer letzten Anweisung verlassen oder in einer Prozedur nichts zurückgeben möchte.

Ressourcen

Die erste Seite, die man besuchen sollte, wenn man sich für Ruby interessiert, ist http://www.ruby-lang.org. Ruby hat in letzter Zeit wegen des Frameworks Ruby on Rails sehr viel an Popularität gewonnen. Mehr erfahren über Ruby on Rails kann man unter http://www.rubyonrails.org/.




1  Auch hier gibt es Parallelen zwischen .NET und Java. Die modernen JVMs benutzen auch einen JIT-Compiler, und es gibt mehrere Programmiersprachen, die einen Compiler für die JVM haben.

2  In Python sind alle Elemente eines Objekts immer öffentlich. Trägt jedoch ein Element einen Namen, der mit zwei Unterstrichen anfängt, erweitert Python diesen Namen um den Namen der definierenden Klasse. Auf diese Art werden potenzielle Namenskonflikte meistens umgangen.

3  Die Methode __str__ müssten wir in diesem Kontext gar nicht angeben. Die Prozedur print würde sie implizit aufrufen, um eine Textrepräsentation des Objekts v zu bekommen.

4  Ja, wir wissen, dass Perlen keine Steine sind und möchten an dieser Stelle die Leistungen der Muscheln nicht verschweigen.

 << zurück
  
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Objektorientierte Programmierung






 Objektorientierte
 Programmierung


Zum Katalog: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Katalog: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Katalog: C++ Handbuch






 C++ Handbuch


Zum Katalog: Einstieg in Python






 Einstieg in Python


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de