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

Inhaltsverzeichnis
1 Einleitung
2 Die Basis der Objektorientierung
3 Die Prinzipien des objektorientierten Entwurfs
4 Die Struktur objektorientierter Software
5 Vererbung und Polymorphie
6 Persistenz
7 Abläufe in einem objektorientierten System
8 Module und Architektur
9 Aspekte und Objektorientierung
10 Objektorientierung am Beispiel: Eine Web-Applikation mit PHP 5 und Ajax
A Verwendete Programmiersprachen
B Literaturverzeichnis
Stichwort
Ihre Meinung?

Spacer
 <<   zurück
Objektorientierte Programmierung von Bernhard Lahres, Gregor Rayman
Das umfassende Handbuch
Buch: Objektorientierte Programmierung

Objektorientierte Programmierung
2., aktualisierte und erweiterte Auflage, geb.
656 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1401-8
Pfeil 4 Die Struktur objektorientierter Software
  Pfeil 4.1 Die Basis von allem: das Objekt
    Pfeil 4.1.1 Eigenschaften von Objekten: Objekte als Datenkapseln
    Pfeil 4.1.2 Operationen und Methoden von Objekten
    Pfeil 4.1.3 Kontrakte: Ein Objekt trägt Verantwortung
    Pfeil 4.1.4 Die Identität von Objekten
    Pfeil 4.1.5 Objekte haben Beziehungen
  Pfeil 4.2 Klassen: Objekte haben Gemeinsamkeiten
    Pfeil 4.2.1 Klassen sind Modellierungsmittel
    Pfeil 4.2.2 Kontrakte: die Spezifikation einer Klasse
    Pfeil 4.2.3 Klassen sind Datentypen
    Pfeil 4.2.4 Klassen sind Module
    Pfeil 4.2.5 Sichtbarkeit von Daten und Methoden
    Pfeil 4.2.6 Klassenbezogene Methoden und Attribute
    Pfeil 4.2.7 Singleton-Methoden: Methoden für einzelne Objekte
  Pfeil 4.3 Beziehungen zwischen Objekten
    Pfeil 4.3.1 Rollen und Richtung einer Assoziation
    Pfeil 4.3.2 Navigierbarkeit
    Pfeil 4.3.3 Multiplizität
    Pfeil 4.3.4 Qualifikatoren
    Pfeil 4.3.5 Beziehungsklassen, Attribute einer Beziehung
    Pfeil 4.3.6 Implementierung von Beziehungen
    Pfeil 4.3.7 Komposition und Aggregation
    Pfeil 4.3.8 Attribute
    Pfeil 4.3.9 Beziehungen zwischen Objekten in der Übersicht
  Pfeil 4.4 Klassen von Werten und Klassen von Objekten
    Pfeil 4.4.1 Werte in den objektorientierten Programmiersprachen
    Pfeil 4.4.2 Entwurfsmuster »Fliegengewicht«
    Pfeil 4.4.3 Aufzählungen (Enumerations)
    Pfeil 4.4.4 Identität von Objekten


Rheinwerk Computing - Zum Seitenanfang

4.2 Klassen: Objekte haben Gemeinsamkeiten  Zur nächsten ÜberschriftZur vorigen Überschrift

In einer Anwendung gibt es in der Regel viele Objekte. Manche sind grundverschieden, manche sind strukturell und funktional gleich und unterscheiden sich nur durch ihre Identität und durch die ihnen zugeordneten konkreten Daten.

Gemeinsame Eigenschaften von Objekten

Wir haben in unserem Beispiel aus Listing 4.1 nur ein Objekt implementiert, das eine elektrische Leitung repräsentiert. Die physikalischen Gesetze gelten aber für alle elektrischen Leitungen. Alle elektrischen Leitungen weisen bestimmte Eigenschaften auf, wenn auch mit unterschiedlichen Werten. Alle folgen denselben Regeln, alle können ihre Daten in gleichen Datenstrukturen speichern, und deren Funktionalität kann durch die gleichen Methoden implementiert werden. In einem objektorientierten System ist es sinnvoll, diese Gemeinsamkeiten zu erfassen und zu modellieren.

Gleichartige Objekte können deshalb zu Klassen zusammengefasst werden.


Icon Hinweis Klassen in der objektorientierten Programmierung

Eine Klasse beschreibt die gemeinsamen Eigenschaften und Operationen einer Menge von gleichartigen Objekten.

Die Objekte, die zu einer Klasse gehören, werden als Exemplare dieser Klasse bezeichnet (englisch instance).


Alle Objekte, die in unserer Anwendung eine elektrische Leitung abbilden, gehören zu derselben Klasse. Sie sind Exemplare der Klasse ElektrischeLeitung. Wir werden im folgenden Abschnitt zeigen, wie Klassen zur Modellierung und Strukturierung einer Anwendung eingesetzt werden können.


Rheinwerk Computing - Zum Seitenanfang

4.2.1 Klassen sind Modellierungsmittel  Zur nächsten ÜberschriftZur vorigen Überschrift

Objekte und Exemplare von Klassen

Klassen sind für die objektorientierte Programmierung so fundamental, dass in den meisten Programmiersprachen ein Objekt immer einer konkreten Klasse zugeordnet wird. [JavaScript ist hier eine der wenigen Ausnahmen, deswegen haben wir diese Sprache für unser erstes Beispiel gewählt. ] Damit fallen die Begriffe Objekt und Exemplar zusammen und werden oft (nicht ganz korrekt) als Synonyme verwendet. Der Unterschied ist, während das Wort »Objekt« keine Aussage über die Zugehörigkeit des Objekts zu einer Klasse beschreibt, wird das Wort »Exemplar« immer im Zusammenhang mit einer Klasse verwendet.

Bei der Entwicklung eines objektorientierten Systems steht ganz am Anfang die Aufgabe, die Objekte, die im System benötigt werden, zu identifizieren. Gleich danach kommt aber die Aufgabe, die Gemeinsamkeiten dieser Objekte festzustellen und sie Klassen zuzuordnen, die sogenannte Klassifizierung.

Unser Anliegen, eine Anwendung klar zu strukturieren, besteht also zum größten Teil darin, sowohl die äußere Struktur der Klassen untereinander als auch die innere Struktur der Elemente einer Klasse klar und übersichtlich zu gestalten.


Icon Hinweis Klassifizierung

Die Zuordnung von Objekten zu Klassen heißt Klassifizierung. Dabei werden relevante Gemeinsamkeiten von Objekten identifiziert und Objekte mit diesen Gemeinsamkeiten derselben Klasse zugeordnet.


Abbildung 4.11    Mengendarstellung für Klassen

In Abbildung 4.11 liegt eine Klassifizierung für eine Reihe von Tieren und anderen Objekten vor, die zum großen Teil eine Rolle in Fernsehserien, Filmen oder Büchern gespielt haben. Die in diesem Beispiel gewählte Zuordnung zu Klassen ist aber nur eine von vielen möglichen Zuordnungen.

Sie sehen auch, dass in dieser Zuordnung einige Objekte zu mehreren Klassen gehören. So sind zum Beispiel Clarence und Alex sowohl der Klasse Filmstars als auch den Klassen Löwen und Tiere zugeordnet. Ist ein Objekt einer Klasse zugeordnet, sagt man auch, das Objekt sei ein Exemplar dieser Klasse.


Icon Hinweis Exemplare einer Klasse

Objekte sind Exemplare der Klassen, zu denen sie gehören. Dabei kann eine Klasse mehrere Exemplare haben, und Objekte können auch Exemplare von mehreren Klassen sein.

In den meisten Programmiersprachen ist ein Objekt immer nur ein direktes Exemplar einer einzigen Klasse. Über Beziehungen zwischen Klassen, die wir in Kapitel 5, »Vererbung und Polymorphie«, vorstellen werden, kann ein Objekt aber auch indirekt Exemplar von weiteren Klassen sein. Im weiteren Verlauf werden wir generell den Begriff Exemplar verwenden. Zwischen direkten und indirekten Exemplaren werden wir nur unterscheiden, wenn diese Differenzierung für eine Problemstellung relevant ist.


Statt des Begriffs Exemplar wird in der Literatur und im täglichen Gespräch unter Softwareentwicklern häufig auch der Begriff Instanz einer Klasse verwendet. Dieser leitet sich allerdings aus einer inkorrekten Übersetzung des englischen Begriffs Instance ab. Da sich der Begriff Exemplar als eine besser gelungene Übersetzung etabliert hat, werden wir diesen im Folgenden verwenden.


Icon Hinweis Einfache und mehrfache Klassifizierung

Wenn ein Objekt immer nur direktes Exemplar einer einzigen Klasse sein kann, spricht man von der einfachen Klassifizierung. Die meisten objektorientierten Programmiersprachen unterstützen nur die einfache Klassifizierung. Wenn ein Objekt direkt mehreren konkreten Klassen zugeordnet werden kann, spricht man von mehrfacher Klassifizierung.


Zwei Modellebenen

Bei der Klassifizierung gibt es generell zwei verschiedene Ebenen, auf denen Objekte zu Klassen gruppiert werden:

  • das konzeptionelle Modell (oder Analysemodell)
  • das Implementierungsmodell (oder Designmodell)

Wir werden uns in den folgenden Kapiteln hauptsächlich mit dem Implementierungsmodell beschäftigen. Dieses wird jedoch häufig auf Basis eines konzeptionellen Modells erstellt.


Icon Hinweis Konzeptionelles Modell (Analysemodell)

In einem konzeptionellen Modell beschreibt eine Klasse die konzeptionellen Gemeinsamkeiten von bestimmten Objekten, deren Rollen, deren Verwendung und deren Verantwortlichkeiten. Die erstellten Konzepte bleiben unabhängig von der Technologie, in der Software realisiert werden soll. Sie beschreiben die Fachdomäne und nicht die Software.

Klassen dienen im konzeptionellen Modell dazu, die Begriffe, die Beziehungen und Prozesse der Fachdomäne zu kategorisieren und zu strukturieren. Die Aufgabe, das konzeptionelle Modell zu erstellen, wird als Analysephase bezeichnet. Sie wird häufig nicht von Softwareentwicklern, sondern von separaten Teams in Abstimmung mit den fachlichen Anforderern durchgeführt.


In der Regel wird das konzeptionelle Modell nicht direkt in ein Programm umgesetzt werden. Dies liegt daran, dass für die Strukturierung von Software andere Kriterien angelegt werden als für die Strukturierung einer fachlichen Domäne.

Zum Beispiel kann es sinnvoll sein, eine Operation kündigen(), die im fachlichen Modell einem Vertrag zugeordnet ist, in der Implementierung so umzusetzen, dass die Kündigung als eine eigene Klasse modelliert wird. Es resultiert ein Modell, das sich mit einer Programmiersprache umsetzen lässt, das Implementierungsmodell (oder auch Designmodell).


Icon Hinweis Das Implementierungsmodell

Ein Implementierungsmodell legt die konkrete Umsetzung des konzeptionellen Modells fest. Manche Klassen aus dem konzeptionellen Modell können Klassen im Implementierungsmodell direkt entsprechen. Häufig wird es keinen direkten Bezug zu Klassen der Implementierung geben. Abhängig von gewählter Architektur und Programmiersprache werden bestimmte Konzepte unterschiedlich komplex realisiert.Wir werden im Folgenden die Klassen immer auf der Ebene des Implementierungsmodells betrachten.


Klassen haben eine Spezifikation.

Klassen haben in einem Implementierungsmodell zwei unterschiedliche Funktionen.

  • Zum einen können Sie eine Spezifikation auf der Basis der Klassen erstellen. Dabei entspricht die Klasse einem abstrakten Typ: Sie beschreibt exakt die Schnittstelle für alle Exemplare der Klasse. Diese Rolle der Klassen werden wir im folgenden Abschnitt 4.2.2 genauer erläutern.
  • Zum anderen ist einer Klasse aber auch eine Implementierung zugeordnet. Sie kann damit als Modul verwendet werden, das eine bestimmte Umsetzung ihrer Spezifikation kapselt. In Abschnitt 4.2.4 werden wir auf diese Sichtweise genauer eingehen.

Bevor wir also dazu übergehen, wie für Klassen ihre zugehörige Spezifikation formuliert werden kann, werfen wir noch einen Blick auf ein Beispiel einer Klasse. In Abschnitt 4.1.1 haben wir bereits ein Objekt vorgestellt, das eine elektrische Leitung repräsentierte. In Abbildung 4.12 sind dieses Objekt und die Klasse, zu der es gehört, dargestellt.

Abbildung 4.12    Die Klasse der elektrischen Leitungen

Die Klasse ElektrischeLeitung ist dabei mit ihrem Namen und ihrem Stereotyp aufgeführt. Darunter sind abgetrennt zunächst die Datenelemente und dann die Operationen und Methoden der Klasse aufgelistet. Das Datenelement spannung ist dabei mit dem Wert 230 vorbelegt. Der Stereotyp <<implementationsklasse>> beschreibt, dass es sich um ein Implementierungsmodell handelt. Stereotypen bieten in UML die Möglichkeit, die Beschreibungssprache zu erweitern, um genauere Angaben zu bestimmten Elementen zu machen.

Außerdem ist in der Abbildung das Objekt leitung1 angegeben, mit seiner Zugehörigkeit zur Klasse ElektrischeLeitung. Zwischen dem Objekt und der Klasse ist eine Abhängigkeitsbeziehung eingezeichnet, die mit dem Stereotyp <<instance>> markiert ist. Dies bedeutet, dass leitung1 ein Exemplar der Klasse ElektrischeLeitung ist. Die Werte für die Datenelemente spannung und widerstand sind beim Objekt mit aufgeführt.


Rheinwerk Computing - Zum Seitenanfang

4.2.2 Kontrakte: die Spezifikation einer Klasse  Zur nächsten ÜberschriftZur vorigen Überschrift

In Abschnitt 4.1.3 haben Sie gesehen, dass zwischen Objekten Kontrakte geschlossen werden, die deren Zusammenarbeit regeln. Diese Kontrakte lassen sich natürlich auch auf Klassen anwenden. Kontrakte, die für Klassen definiert werden, gelten für alle Exemplare dieser Klassen.

Dabei legen Klassen die Eigenschaften, Operationen und Datenelemente für alle ihre Exemplare fest.


Icon Hinweis Schnittstelle einer Klasse

Klassen legen für alle ihre Exemplare fest, welche Eigenschaften und Operationen diese Exemplare besitzen. Die Schnittstelle einer Klasse besteht damit aus allen durch die Klasse definierten Eigenschaften und Operationen. Außerdem werden durch die Schnittstelle die Vor- und Nachbedingungen für die Operationen beschrieben sowie geltende Invarianten für Beziehungen zwischen den Eigenschaften von Exemplaren der Klasse.

Einige Programmiersprachen bieten Konstrukte an, die explizit sogenannte Schnittstellen-Klassen definieren. Es ist aber zu beachten, dass jede Klasse eine Schnittstelle definiert. Reine Schnittstellen-Klassen haben dabei lediglich die Restriktion, dass sie nicht gleichzeitig eine Implementierung der Schnittstelle anbieten können.


In Abbildung 4.12 legt die Klasse ElektrischeLeitung fest, dass alle ihre Exemplare, so auch das dargestellte Exemplar leitung1, die Datenelemente spannung und widerstand besitzen. Außerdem legt die Klasse fest, dass die Exemplare die Operationen getSpannung(), setSpannung() und getStromstärke() besitzen.

Ein Kontrakt zwischen einer Klasse und den Nutzern einer Klasse bezieht sich auf die Operationen dieser Klasse und auf Abhängigkeiten zwischen den Eigenschaften der Klasse. Für die zur Verfügung gestellten Operationen legt ein Kontrakt fest, welche Voraussetzungen ein Aufrufer der Operation schaffen muss, damit die Operation durchgeführt werden kann: die Vorbedingungen müssen eingehalten werden. Außerdem legt ein Kontrakt fest, welche Leistung die Operation erbringt, sofern die Vorbedingungen gegeben sind: Die Nachbedingungen werden festgelegt.


Icon Hinweis Vorbedingungen (engl. Preconditions) einer Operation

Für eine Operation können Vorbedingungen festgelegt werden. Ein Aufrufer der Operation verpflichtet sich, diese Bedingungen beim Aufruf der Operation herzustellen. Sind die Vorbedingungen nicht erfüllt, ist die Operation nicht verpflichtet, ihre spezifizierte Aufgabe zu erfüllen. Vorbedingungen sind damit der Kontraktbestandteil, den der Aufrufer einer Operation einzuhalten hat.


Sind die Vorbedingungen eingehalten, ist der Aufgerufene wiederum verpflichtet, die Nachbedingungen herzustellen.


Icon Hinweis Nachbedingungen (engl. Postconditions) einer Operation

Für eine Operation können Nachbedingungen festgelegt werden. Eine Klasse, welche die Operation über eine Methode umsetzt, sichert zu, dass die Nachbedingungen unmittelbar nach Aufruf der Operation gelten. Diese Zusicherung gilt jedoch nur, wenn der Aufrufer die für die Operation definierten Vorbedingungen eingehalten hat.


Zusätzlich können auch nach außen zugesicherte Bedingungen vereinbart sein, die immer gelten sollen, unabhängig vom Aufruf einer Operation.


Icon Hinweis Invarianten (engl. Invariants)

Invarianten sind Eigenschaften und Beziehungen zwischen Eigenschaften eines Objekts, die sich durch keine Operation ändern lassen. Invarianten, die für eine Klasse definiert werden, gelten für alle Exemplare dieser Klasse.


Die Bedingungen müssen dabei möglichst in einer Form ausgedrückt werden, die sie automatisch überprüfbar machen.

Auf Ebene der UML ist die sogenannte Object Constraint Language (OCL) dafür vorgesehen, Bedingungen auszudrücken. [Eine sehr übersichtliche Beschreibung zur OCL und ihrer Abbildung auf Java-Programme findet sich in einer Beschreibung von Jason Gorman unter http://www.parlezuml.com/tutorials/umlforjava/java_ocl.pdf. ]

Object Constraint Language (OCL)


Icon Hinweis Object Constraint Language (OCL)

OCL ist seit der Version 1.1 von UML Bestandteil des UML-Standards. OCL wird dabei verwendet, um zusätzliche Bedingungen darzustellen, die sich mit den sonstigen Beschreibungsmitteln der UML nicht oder nur umständlich ausdrücken lassen.

OCL stellt dabei eine rein deklarative Beschreibungsmöglichkeit zur Verfügung, um sogenannte Constraints auszudrücken. Dabei ist ein Constraint ein Ausdruck, der entweder wahr oder falsch ist. Damit eignet sich OCL sehr gut, um Vorbedingungen, Nachbedingungen und Invarianten von Operationen auszudrücken.4


Die Syntax der OCL ist recht intuitiv, so dass die angegebenen Bedingungen meist direkt verständlich sind.

OCL: Beispiel

Abbildung 4.13 zeigt ein einfaches Beispiel für Vor- und Nachbedingungen, wie sie in OCL ausgedrückt werden. Durch die Angabe von context ZeitungsAbo::kuendigen wird ausgedrückt, dass sich die gelisteten Bedingungen auf die Operation kuendigen der Klasse ZeitungsAbo beziehen. Mit pre: wird eine Vorbedingung gekennzeichnet, in diesem Fall wird verlangt, dass das betroffene Abo nicht bereits im Status gekuendigt ist und dass das angegebene Datum nicht vor dem frühestmöglichen Kündigungsdatum liegt. Mit post: wird die Nachbedingung gekennzeichnet, in diesem Fall wird zugesichert, dass sich das Abo danach im Zustand gekuendigt befindet.

Abbildung 4.13    Beispiel für Bedingungen in OCL-Notation

Mit den nun eingeführten Begriffen von Vorbedingung, Nachbedingung und Invariante lässt sich jetzt auch definieren, was eigentlich die Spezifikation einer Klasse ausmacht.


Icon Hinweis Spezifikation einer Klasse

Die Spezifikation einer Klasse beschreibt für alle Operationen dieser Klasse deren Vor- und Nachbedingungen. Zusätzlich enthält die Spezifikation eine Beschreibung der Invarianten, die für alle Exemplare der Klasse gelten. Damit lässt sich die Spezifikation einer Klasse in Form von Vorbedingungen, Nachbedingungen und Invarianten formulieren.


Eine Klasse legt also Kontrakte für ihre Exemplare fest. In 7.5, »Kontrakte: Objekte als Vertragspartner«, werden Sie ausführliche Beispiele kennen lernen, bei denen Kontrakte formuliert und auch überprüft werden.

Wir gehen aber zunächst auf eine weitere Funktion der Klassen ein: Sie legen den Datentyp für alle ihre Exemplare fest. Wir beschreiben im Folgenden, wie sich Klassen in die unterschiedlichen Typsysteme von Programmiersprachen einfügen.


Rheinwerk Computing - Zum Seitenanfang

4.2.3 Klassen sind Datentypen  Zur nächsten ÜberschriftZur vorigen Überschrift

Eine Klasse legt fest, welche Eigenschaften und Operationen bei einem Objekt genutzt werden können, das dieser Klasse angehört. Damit legt die Klasse den Typ für alle ihre Exemplare fest. Ein Objekt kann auch Exemplar von mehreren Klassen sein, in diesem Fall hat das Objekt mehrere Typen zugeordnet.

Im Beispiel zur Klasse der elektrischen Leitungen aus Abschnitt 4.2.1 spezifiziert die Klasse ElektrischeLeitung, dass jedes zu dieser Klasse gehörige Objekt eine Eigenschaft spannung hat, auf die über die Operation getSpannung zugegriffen werden kann. Über die Umsetzung dieser Eigenschaft sagt die Spezifikation der Klasse nichts aus. Trotzdem kann nun aber das sogenannte Typsystem einer Programmiersprache aufgrund dieser Information entscheiden, ob die Operation getSpannung auf einem Objekt zulässig ist.

Für die Entscheidung, ob ein Objekt einer Variablen zugewiesen oder ob eine Operation auf einem Objekt durchgeführt werden kann, sind die Typen der Objekte wichtig. Es ist sinnvoll, eine elektrische Leitung nach der daran anliegenden Spannung zu fragen. Diese Frage einem Objekt zu stellen, das kein Exemplar der Klasse ElektrischeLeitung ist, wäre aber ein Fehler.


Icon Hinweis Typsysteme der Programmiersprachen

Ein Typsystem ist ein Bestandteil der Umsetzung einer Programmiersprache oder deren Laufzeitumgebung, der die im Programm verwendeten Datentypen zur Übersetzungszeit oder Laufzeit überprüft. Dabei wird jedem Ausdruck ein Typ zugeordnet. Das Typsystem stellt dann fest, ob der Ausdruck in einer bestimmten Verwendung mit den Regeln des Typsystems verträglich ist.

Zu diesen Prüfungen gehört zum Beispiel auch, ob eine Operation auf einem Objekt durchgeführt oder ob ein Objekt einer Variablen zugeordnet werden kann.

Je nachdem, wann programmtechnisch diese Prüfung stattfindet, unterscheiden wir die Typsysteme der Programmiersprachen:

  • Beim statischen Typsystem erfolgt die Überprüfung zur Übersetzungszeit des Programms.
  • Beim dynamischen Typsystem erfolgt die Prüfung zur Laufzeit des Programms.

In den folgenden beiden Abschnitten stellen wir das statische und das dynamische Typsystem jeweils kurz an einem Beispiel vor.

Statisches Typsystem

In einem statischen Typsystem wird der Typ von Variablen und Parametern im Quelltext deklariert. In statisch typisierten Programmiersprachen gehören nicht nur die Objekte bestimmten Typen an, die Variablen sind auch bestimmten Typen zugeordnet. Eine Variable, für die ein Typ deklariert ist, schränkt ein, welche Objekte ihr zugeordnet werden können.

Compiler erkennen Typkonflikte.

In einer statisch typisierten Programmiersprache erkennt bereits der Compiler, wenn wir einer Variablen ein Objekt eines unpassenden Typs zuweisen möchten, und meldet einen Kompilierungsfehler. Es braucht deshalb nicht zur Laufzeit überprüft zu werden, ob eine Operation auf einem Objekt durchgeführt werden kann, denn es ist sichergestellt, dass einer Variablen nur Objekte des passenden Typs zugewiesen worden sind. So kann bereits der Compiler anhand des deklarierten Typs überprüfen, ob die aufgerufene Operation zur Spezifikation der deklarierten Klasse gehört.

Vorteile des statischen Typsystems

Das statische Typsystem hat gegenüber dem dynamischen Typsystem einige Vorteile:

  • Kompilierte Anwendungen können besser optimiert werden: Da der Compiler bereits überprüfen kann, ob eine Operation zulässig ist, braucht diese Überprüfung nicht zur Laufzeit stattzufinden.
  • Die Programmstruktur ist übersichtlicher: Da jede Variable einen deklarierten Typ hat, wird schneller klar, welche Rolle welche Variable spielt.
  • Entwicklungsumgebungen können besser unterstützt werden: Da die Entwicklungsumgebung die Typen der Variablen kennt, kann sie dem Entwickler verschiedenartige Unterstützung anbieten, zum Beispiel zu einer Objektvariablen die möglichen Operationen anzeigen.
  • Fehler im Programm können früher erkannt werden: Da der Compiler die Typen überprüft, werden Situationen, in denen Sie eckige Klötzchen in runde Löcher stecken, früh erkannt.

Java

Betrachten wir ein Beispiel, bei dem ein Exemplar der Klasse ElektrischeLeitung zum Einsatz kommt. Die Klasse ist in Abbildung 4.14 aufgeführt.

Wenn Sie in Java einer Variablen ein neu erstelltes Exemplar der Klasse zuweisen wollen, muss diese Variable den korrekten Typ aufweisen.

Abbildung 4.14    Klasse »ElektrischeLeitung« mit Exemplar

ElektrischeLeitung wire = new ElektrischeLeitung(220,50);  
String text = wire.getBeschreibung();   
int textLength = text.length();   
text = wire;     // Fehler: Typen nicht kompatibel

In Zeile weisen Sie der Variablen wire eine Referenz auf das neu erstellte Exemplar der Klasse ElektrischeLeitung zu.

In Zeile braucht zur Laufzeit nicht überprüft zu werden, ob das von wire referenzierte Objekt die Operation getBeschreibung() unterstützt, denn wir wissen bereits zur Übersetzungszeit, dass Objekte des Typs ElektrischeLeitung eine solche Operation unterstützen und das Ergebnis dieser Operation vom Typ String ist. Ähnliches gilt für Zeile . Der Fehler in Zeile wird bereits zur Übersetzungszeit entdeckt. Der Compiler weiß, dass die Variable wire nur ein Objekt vom Typ ElektrischeLeitung, nicht aber vom Typ String enthalten kann.

Kopplung zwischen Klassen und Typen

In einer statisch typisierten Sprache sind die Klassen mit dem Typsystem sehr eng gekoppelt. Jede Klasse deklariert einen Typ. Je nach Programmiersprache kann das Typsystem zusätzlich noch andere Typen enthalten, die nicht den deklarierten Klassen entsprechen – Java oder C++ enthalten zum Beispiel primitive Datentypen wie int oder char.

Dynamisches Typsystem

Eine andere Strategie verfolgen die Sprachen mit dynamischem Typsystem. In diesen Programmiersprachen sind Variablen keinen deklarierten Typen zugeordnet. Die Variablen können beliebige Objekte referenzieren, ob eine Operation auf dem referenzierten Objekt durchgeführt werden kann, wird dynamisch zur Laufzeit des Programms überprüft.

Dynamische Typisierung nur als Dokumentation

Eigentlich ist die Bezeichnung dynamisch typisiert nicht ganz korrekt, denn die Typen werden in diesen Sprachen gar nicht deklariert. Die Variablen haben keinen deklarierten Typ und die Klassen beziehungsweise die Objekte auch nicht. Sie bieten bestimmte Operationen an, aber welche dieser Operationen zu einem Typ gehören, ist nicht Bestandteil des Programms. Diese wichtige Information gehört bei dynamisch typisierten Sprachen zur Dokumentation.

Vorteile des dynamischen Typsystems

Auch das dynamische Typsystem hat Vorteile:

  • Das dynamische Typsystem ist flexibler: Da die Variablen, die Parameter und die Ergebnisse der Funktionen keine deklarierten Parameter haben, können Sie die Funktionen mit einer größeren Vielfalt von Objekten verwenden. Man spricht auch von Ducktyping, also einem Enten-Typsystem: Wenn es watschelt wie eine Ente, wenn es schwimmt wie eine Ente, wenn es quakt wie eine Ente, so behandeln wir es wie eine Ente.
  • Beim dynamischen Typsystem entfällt die Notwendigkeit einer expliziten Typumwandlung.

Programmiersprache Python (dynamisch typisiert)

Unten stehend ein Beispiel in der dynamisch typisierten Sprache Python. Wir verwenden dabei wieder ein Exemplar der Klasse ElektrischeLeitung aus Abbildung 4.14.

  wire = ElektrischeLeitung(220,50)  
  text = wire.getBeschreibung()  
  textLength = len(text) # Entspricht text.__len__()  
  text = wire  
  textLength = len(text) # Entspricht name.__len__() 

Listing 4.3    Zuweisung von Objekten auf Variablen in Python

Hier wird in Zeile überprüft, ob das Objekt, das von wire referenziert wird, tatsächlich die Operation getBeschreibung unterstützt. Im Unterschied zu der Version in Java kommt es in Zeile zu keinem Fehler, denn in Python können wir der Variablen text nicht nur Zeichenketten, sondern auch ein Exemplar von ElektrischeLeitung zuweisen. Dafür kommt es zu einem Laufzeitfehler in Zeile , denn in Zeile referenziert die Variable text ein Exemplar von ElektrischeLeitung, und dieses unterstützt die Operation len(..) nicht.

Klassen und Typsystem entkoppelt

In einer dynamisch typisierten Sprache spielen Klassen für das Typsystem eine untergeordnete Rolle. Da die Typen nicht explizit deklariert werden, können auch Objekte, die zu ganz unterschiedlichen Klassen gehören, den gleichen Typ, das heißt die gleichen Operationen, implementieren.

In Abschnitt 5.1.5, »Vererbung der Spezifikation und das Typsystem«, werden wir darauf eingehen, warum ein dynamisches Typsystem es einfacher macht, Änderungen an Klassenhierarchien vorzunehmen.

Zunächst werfen wir aber im folgenden Abschnitt einen Blick auf ein Problem, das nur in statisch typisierten Sprachen existiert: Es ist schwierig, die Gemeinsamkeiten verschiedener Klassen zusammenzufassen, wenn sich diese nur in Bezug auf den Typ eines verwendeten Objekts unterscheiden.

Parametrisierte Klassen

In statisch typisierten Sprachen kann es oft ein Problem sein, dass für gleichartige Abläufe, die sich lediglich im Typ eines Parameters unterscheiden, redundanter Code erstellt werden muss. Wir werden dieses Problem gleich an einem Beispiel vorstellen. Parametrisierte Klassen unterstützen bei der Lösung dieses Problems, deshalb präsentieren wir hier zunächst deren Definition.


Icon Hinweis Parametrisierte Klassen

Bei der Deklaration von parametrisierten Klassen können ein oder mehrere Typparameter angegeben werden. Wird ein Exemplar so einer Klasse erstellt, muss für diesen Parameter ein konkreter Typ angegeben werden. Zur Übersetzungszeit wird der Parameter dann durch den konkret angegebenen Typ ersetzt.

Eine Klassendeklaration, die mit Typparameter versehen ist, deklariert nicht nur eine Klasse, sondern eine Menge von Klassen, die sich durch den konkreten Wert der Typparameter unterscheiden.


Abbildung 4.15 zeigt ein Beispiel dafür, wie parametrisierte Klassen in UML dargestellt werden.

Abbildung 4.15    Beispiel für die UML-Darstellung von parametrisierten Klassen

Die Klasse Vector, die in Abbildung 4.15 dargestellt ist, ist in Java ab Version 5 eine parametrisierte Klasse. Ihre Operationen haben zum Teil einen Parameter element, dessen Typ noch nicht festgelegt ist. Wenn ein Exemplar der Klasse Vector angelegt wird, wird dieser Typparameter T an einen konkreten Typ (in der Regel eine Klasse) gebunden. Es ist auch möglich, dass eine weitere Klasse, wie in diesem Beispiel die Klasse StringVector, den Typparameter bereits bindet. Alle Exemplare von StringVector arbeiten damit automatisch mit dem Typ String als Elementtyp.

Um die Verwendung von parametrisierten Klassen zu illustrieren, drängt es sich geradezu auf, zwei Beispiele in der Sprache Java zu präsentieren. Da Java erst ab der Version 5 parametrisierte Klassen anbietet, lässt sich ein Vergleich eines Stücks Source-Code ohne und mit parametrisierten Klassen gut anstellen.

Hier zunächst die Variante unter Verwendung von Java 1.4. Die Darstellung der Klasse Vector ist in Abbildung 4.16 zu finden.

Code ohne parametrisierte Klassen

Abbildung 4.16    Klasse »Vector« in Java 1.4

Da eine Parametrisierung nicht möglich ist, arbeitet die Klasse Vector auf allen Exemplaren der Klasse Object. Im Fall von Java sind alle Objekte Exemplare dieser Klasse.

ElektrischeLeitung wire = new ElektrischeLeitung(220,50); 
Vector wires = new Vector(); 
wires.add(wire); 
ElektrischeLeitung wire2 =                 
        (ElektrischeLeitung) wires.get(0);        

Dieser Code hat zwei Nachteile. Zum einen müssen Sie beim Zugriff auf das erste Element des Vektors in Zeile eine explizite Konvertierung zur Klasse ElektrischeLeitung machen. Der Hauptnachteil ist aber, dass Sie nicht sicher sein können, dass in dem Vektor tatsächlich nur Exemplare der Klasse ElektrischeLeitung enthalten sind. Den vorgestellten Zeilen lässt sich das zwar entnehmen, aber in realen Szenarien wird so ein Vektor möglicherweise auch mit anderen Objekten bestückt. Sie laufen also immer Gefahr, dass die Konvertierung des enthaltenen Elements in die Klasse ElektrischeLeitung in Zeile fehlschlägt. Der Zugriff auf die Sammlung ist nicht typsicher.

Code mit parametrisierten Klassen

Schauen Sie sich nun die Variante unter Verwendung einer parametrisierten Klasse in Java 5 an, die nun die Version der Klasse Vector aus Abbildung 4.15 verwendet.

ElektrischeLeitung leitung = 
          new ElektrischeLeitung(220,50); 
Vector<ElektrischeLeitung> leitungen =    
         new Vector ElektrischeLeitung ();        
leitungen.add(leitung); 
ElektrischeLeitung leitung2 = leitungen.get(0);   

Hier haben Sie dem Compiler in Zeile gesagt, dass der Vektor leitungen nur Objekte vom Typ ElektrischeLeitung enthalten kann. Daher »weiß« der Compiler in Zeile , dass die Operation get ein Exemplar von ElektrischeLeitung zurückgibt.

Die Klasse Vector ist aus der Sicht des Typsystems zur Übersetzungszeit eine ganze Familie von Klassen – eine für jeden Typ der Objekte, die sie enthalten kann.

Verwenden Sie wie in unserem Beispiel die Klasse Vector<ElektrischeLeitung>, so wird der Typ ElektrischeLeitung als der Wert des Typparameters T eingesetzt. Der Typparameter T wurde für die Klasse Vector in Abbildung 4.15 definiert. Der Aufruf leitungen.add(...) akzeptiert nur Parameter vom Typ ElektrischeLeitung, und der Typ der übergebenen Variablen leitung wird bereits zur Übersetzungszeit überprüft.

Dies funktioniert so, als ob die Operation add als add(ElektrischeLeitung o) deklariert wäre. Der Aufruf leitungen.get(0) funktioniert, als ob die Methode mit ElektrischeLeitung get(..) deklariert wäre. Aus diesem Grunde ist an dieser Stelle keine explizite Typumwandlung nötig.

Parametrisierte Klassen in Java, C# und C++

In den Programmiersprachen firmiert die Fähigkeit, parametrisierte Klassen zu verwenden, unter verschiedenen Namen. Java und C# nennen sie Generics, in C++ wird sie Templates genannt.

In Java bestehen die konkreten Ausprägungen der parametrisierten Klassen nur im Quelltext. Zur Übersetzungszeit wird die Korrektheit der Quelltexte überprüft, und die nötigen Typumwandlungen werden hinzugefügt. Anschließend wird in der übersetzten Version die generische, nicht konkret parametrisierte Klasse verwendet.

In C++ wird im Gegensatz zu Java für jede spezifische Ausprägung eines Templates eine eigene kompilierte Einheit gebildet.

Stark und schwach typisierte Programmiersprachen

Ein anderes Kriterium, nach dem Sie die Typsysteme der Programmiersprachen vergleichen können, ist die Strenge, mit der beliebige Speicherbereiche als Daten eines Objekts behandelt werden können. Die Unterscheidung zwischen starker und schwacher Typisierung ist völlig unabhängig von der Unterscheidung zwischen statischer und dynamischer Typisierung.


Icon Hinweis Stark und schwach typisierte Sprachen

Eine stark typisierte Sprache überwacht das Erstellen und den Zugriff auf alle Objekte so, dass sichergestellt ist, dass Variablen immer auf Objekte verweisen, die auch die Spezifikation des Typs erfüllen, der für die Variable deklariert ist.

Schwach typisierte Sprachen haben diese Restriktion nicht. In solchen Sprachen ist es möglich, ein Objekt einer Variablen zuzuordnen, ohne dass das Objekt notwendigerweise die Spezifikation des Typs der Variablen erfüllt.


Eine statisch stark typisierte Sprache überprüft bei einer Typumwandlung zur Übersetzungszeit eines Programms, ob das Objekt dem Zieltyp der Umwandlung entspricht, und meldet einen Fehler, wenn das nicht der Fall sein soll.

Java: statisch und stark typisiert

Ein Beispiel einer statisch und stark typisierten Sprache ist Java. In Java ist es nicht ohne weiteres möglich, einen beliebigen Speicherbereich als Repräsentation eines Objekts zu interpretieren. Da Java statisch typisiert ist, ist es ein Übersetzungsfehler, wenn man einer Variablen vom Typ String eine Variable vom Typ Object ohne eine explizite Typumwandlung zuweist. Ob die explizite Typumwandlung zur Laufzeit klappt, hängt davon ab, ob das Objekt tatsächlich den Zieltyp der Umwandlung implementiert.

Wenn wir von den primitiven Datentypen in Java absehen, heißt das, dass die Typumwandlung nur dann klappt, wenn das Objekt zu der angegebenen Klasse der Typumwandlung gehört. Gehört das Objekt nicht dazu, führt das in Java zu einem Laufzeitfehler.

Object o1 = new String("test"); 
Object o2 = new Integer(1);
// gelingt, da das von o1 referenzierte Objekt 
// ein String ist 
String s = (String) o1; 
 
// ein Laufzeitfehler. Das von o2 referenzierte Objekt 
// ist kein String 
s = (String) o2;

Listing 4.4    Fehler bei Typumwandlung in Java

Ruby, Smalltalk: dynamisch und stark typisiert

Java ist also nicht nur statisch, sondern auch stark typisiert. Andere Beispiele von stark typisierten Sprachen sind die dynamisch typisierten Sprachen Python, Ruby, JavaScript, Smalltalk sowie die statisch typisierte Sprache C#. [C# ist stark typisiert, wenn man keine unsafe-Sektionen verwendet. In den Sektionen, die mit dem Schlüsselwort unsafe gekennzeichnet sind, kann man Zeiger verwenden und auf beliebige Speicherbereiche zugreifen, sofern es nicht zu einer Zugriffsverletzung führt. Dies kann nützlich sein, ist aber eben unsafe. ]

Eine schwach typisierte Sprache überlässt dem Entwickler die Kompetenz zu entscheiden, ob ein Speicherbereich direkt als ein Objekt zu interpretieren ist. So funktioniert in einer schwach typisierten Sprache auch eine Typumwandlung von Objekten, die nicht zum Zieltyp der Umwandlung passen. Nach der Typumwandlung wird der Speicherbereich, der sich an der Adresse des Objekts befindet, einfach als Exemplar einer anderen Klasse behandelt.

Da C++ die Möglichkeiten zur Typkonvertierung von der Sprache C übernommen hat, wäre dort der unten stehende Aufruf möglich:

MeineKlasse* meinObjekt = new MeineKlasse(); 
String* text = new String("test"); 
text = (String*) meinObjekt;  

Hier würde zur Übersetzungszeit kein Fehler gemeldet, obwohl in Zeile der Variablen text das nach dem Typsystem nicht kompatible Exemplar von MeineKlasse zugewiesen wird. Auch zur Laufzeit wird die Fehlersituation nicht direkt signalisiert. Allerdings wird der erste Zugriff auf die Variable text sehr wahrscheinlich zu einem Programmabsturz führen, da an der Stelle, auf die der Zeiger text verweist, kein String-Objekt zu finden ist, sondern ein Exemplar von MeineKlasse.

C++: statisch und schwach typisiert

In schwach typisierten Sprachen gibt es also eine mögliche Fehlerquelle mehr. Allerdings kann es auch sinnvoll sein, diese Fähigkeit der Programmiersprache zu nutzen. So kann man zum Beispiel in C++ den Inhalt einer Datei einlesen und den Speicherbereich, in den die Datei geladen wurde, sofort als konkretes Objekt nutzen. [Dabei kann natürlich einiges schief gehen, wenn die eingelesenen Daten nicht die erwartete Struktur aufweisen. Das Ergebnis und das resultierende Verhalten des Programms sind dann eher zufällig. ]

Beispiele von schwach typisierten objektorientierten Sprachen sind C++ oder ObjectPascal und Delphi.

Vorteile und Nachteile

Bei der Entscheidung, ob man für die Umsetzung einer Anforderung eine stark oder eine schwach typisierte Sprache bevorzugen sollte, sollte man sich die Frage stellen, ob man einen direkten Zugriff auf bestimmte Speicherbereiche braucht und, wenn ja, ob es nützlich ist, auf diese Daten direkt als Strukturen, denen bestimmte Funktionalität zugeordnet ist, zuzugreifen.

Diskussion: Typisierung von C++

Gregor: C++ soll also schwach typisiert sein. Das ist aber doch nicht ganz korrekt. Wir können doch in C++ zur Laufzeit eine Typumwandlung über einen Aufruf von dynamic_cast durchführen. Dieser Aufruf signalisiert uns doch genau wie in Java einen Fehler, wenn die verwendeten Typen nicht kompatibel sind.

Bernhard: Mit Bezug auf den Operator dynamic_cast hast du Recht. Und wenn dies die einzige Möglichkeit der Typumwandlung in C++ wäre, könnten wir C++ auch als stark typisiert betrachten. Allerdings erbt C++ eine ganze Reihe von Möglichkeiten von der Sprache C. Und die darüber verfügbaren Typkonvertierungen konvertieren jeden beliebigen Typ in jeden beliebigen anderen.Außerdem verwendet C++ für die Bearbeitung von Arrays ähnlich wie C die Zeigerarithmetik. Und so sind bei dem Zugriff auf das zweite Element eines Arrays a die Aufrufe a[1] und *(a+1) äquivalent. Nun, nach dem Prinzip der Ersetzbarkeit kann man einer Zeigervariablen t vom Typ T* auch einen Zeiger s vom Subtyp S* zuweisen. Das funktioniert mit den Arrays allerdings nicht, denn die Exemplare von S können mehr Speicherplatz belegen als die von T. Während also der Ausdruck s[1] tatsächlich auf das zweite Element des Arrays S zugreift, greift t[1] irgendwo in die Mitte der Daten des ersten Elements, in der Annahme, es handele sich um ein direktes Exemplar der Klasse T. C++ ist also keine stark typisierte Programmiersprache.

Neben der Funktion als Modellierungsmittel und Datentyp nehmen Klassen auch eine durchaus bodenständige Aufgabe wahr: Sie sind Module von Software, durch die sich Quelltexte strukturieren lassen. Auf diese Funktion gehen wir im folgenden Abschnitt ein.


Rheinwerk Computing - Zum Seitenanfang

4.2.4 Klassen sind Module  Zur nächsten ÜberschriftZur vorigen Überschrift

Eine Klasse ist oft auch eine Implementierungseinheit, ein Modul eines Programms. Dieses Modul kann auch dazu dienen, die Spezifikation der Klasse umzusetzen. So stellen Klassen zu den festgelegten Operationen meist auch eine zugehörige Implementierung in Form einer Methode bereit.

Eine Klasse übernimmt also die Aufgabe eines Moduls und stellt häufig die folgenden Elemente zur Verfügung:

  • Sie deklariert die Methoden der Klasse.
  • Sie enthält die Implementierung der deklarierten Methoden.
  • Sie deklariert die Datenelemente ihrer Exemplare. [Wie Sie in Abschnitt 4.2.6, »Klassenbezogene Methoden und Attribute«, sehen werden, können Klassen zusätzlich auch Datenelemente deklarieren, die der Klasse selbst zugeordnet sind. ]

Eine Klasse spezifiziert demnach nicht nur eine Schnittstelle, sondern stellt oft auch die Implementierung der Schnittstelle zur Verfügung. Dabei muss die Klasse aber nicht für alle spezifizierten Operationen wirklich eine Methode umsetzen. In Abschnitt 5.1.4, »Abstrakte Klassen, konkrete Klassen und Schnittstellen-Klassen«, werden Sie Klassen kennen lernen, die explizit bestimmte Operationen nur spezifizieren, aber keine Methode dafür bereitstellen.

In Abbildung 4.17 sehen Sie, wie die Klasse ElektrischeLeitung die von ihr spezifizierten Operationen auch in Form von Methoden umsetzt. Die dargestellte Umsetzung ist in Java vorgenommen. Die Klasse selbst fasst dabei in einem Block des Quelltextes alle Datenelemente und Methoden zusammen.

Programmiersprachen unterscheiden sich stark bezüglich der Konzepte, wie sie Klassen zur Strukturierung und Modularisierung von Quelltexten heranziehen. Wir betrachten im Folgenden exemplarisch drei verschiedene Vorgehensweisen am Beispiel der Programmiersprachen Java, Ruby und C++.

Klassen als Module in Java

In Java entspricht eine nach außen sichtbare Klasse einem Modul. Der gesamte Quelltext einer Klasse ist eine zusammenhängende Einheit, ein Abschnitt des Gesamtquelltextes der Anwendung. Eine Klasse kann also nicht über mehrere Bereiche eines Quelltextes verteilt werden.

Abbildung 4.17    Eine Klasse setzt Methoden um.

In Abbildung 4.17 ist für die Umsetzung einer Klasse in Java auch dargestellt, dass die öffentliche Klasse ElektrischeLeitung vollständig in der Datei ElektrischeLeitung.java deklariert und umgesetzt wird. In der gleichen Datei können zwar noch weitere Klassen enthalten sein, diese dürfen aber nicht die Sichtbarkeitsstufe »Öffentlich« (public) aufweisen und damit ihre Schnittstelle nicht nach außen zur Verfügung stellen. [Auf die verschiedenen Sichtbarkeitsstufen werden wir gleich im unmittelbar folgenden Abschnitt Sichtbarkeit von Daten und Methoden eingehen. ]

In Ruby lässt sich eine Klasse in mehrere Quelltext-Module [Auch Ruby verwendet den Begriff »Modul« (Module) in einem engeren Sinn und bezeichnet damit ein spezifisches Sprachkonstrukt; wir meinen damit einfach nur einen zusammenhängenden Abschnitt des Quelltextes, der als eine Einheit betrachtet werden kann. ] aufteilen. In Abbildung 4.18 ist dargestellt, wie die Klasse ElektrischeLeitung aus zwei Bestandteilen zusammengesetzt wird, die in verschiedenen Dateien liegen.

Abbildung 4.18    Kombination der Klasse »ElektrischeLeitung« aus zwei Quelltext-Modulen

Ein vergleichbares Verfahren bieten die partiellen Klassen in C# ab der Version 2. Bei partiellen Klassen werden die Teilquelltexte, aus denen sich eine Klasse zusammensetzt, zur Übersetzungszeit zu einer Binäreinheit zusammengefügt. Ruby ist hier allerdings noch flexibler, eine Klasse kann dort sogar zur Laufzeit noch dynamisch erweitert werden. Durch diese Art der Modularisierung lassen sich Klassen erweitern, ohne in die Quelltexte der bestehenden Klasse eingreifen zu müssen. In Abschnitt 7.2.5, »Fabrikmethoden«, werden Sie ein Beispiel kennen lernen, in dem die Aufteilung einer Klasse auf mehrere Module hilft, Abhängigkeiten zwischen Modulen aufzuheben.

Klassen als Module in C++

Die Sprache C++ bietet eine Zwischenvariante für die Aufteilung einer Klasse auf mehrere Quelltext-Module. Da in C++ die Deklaration einer Klasse von ihrer Implementierung getrennt werden kann, werden dabei üblicherweise Deklaration und Implementierung in separaten Dateien vorgenommen. Dabei ist es auch möglich, die Implementierungen von Methoden einer Klasse auf mehrere Quelltext-Module zu verteilen. Die Deklaration der Klasse muss allerdings in einem Modul vorgenommen werden. [Die Sprache C++ verwendet intensiv einen Precompiler, mit dem man eine Übersetzungseinheit auch in mehrere Dateien aufteilen könnte. So gesehen kann man also auch die Deklaration einer Klasse auf mehrere Dateien verteilen. Wir betrachten hier die Quelltexte eher aus der Sicht des Compilers, für den eine Klassendeklaration in der Tat eine zusammenhängende Einheit sein muss. ]


Rheinwerk Computing - Zum Seitenanfang

4.2.5 Sichtbarkeit von Daten und Methoden  Zur nächsten ÜberschriftZur vorigen Überschrift

Nun werden Sie die Möglichkeiten von objektorientierten Sprachen kennen lernen, die Sichtbarkeit von Daten und Methoden zu steuern. Wenn die Methode eines Objekts für ein anderes Objekt sichtbar ist, so ist dies in diesem Kontext damit gleichbedeutend, dass sie auch aufgerufen werden kann. Sie dient in diesem Fall zur Umsetzung einer Operation.

In den objektorientierten Sprachen können wir differenziert angeben, aus welchen Kontexten bestimmte Operationen von Objekten aufgerufen oder bestimmte Eigenschaften eines Objekts zugegriffen werden dürfen.

Sichtbarkeitsstufen

Werfen Sie also zunächst einen Blick auf die gängigen Sichtbarkeitsstufen in der Übersicht. Die UML definiert vier verschiedene Stufen.

  • Sichtbarkeitsstufe »Öffentlich« (public)
  • Sichtbarkeitsstufe »Privat« (private)
  • Sichtbarkeitsstufe »Geschützt« (protected)
  • Sichtbarkeitsstufe »Bereich« (package)

Wir betrachten hier zunächst die beiden Sichtbarkeitsstufen »Öffentlich« und »Privat«. Diese differenzieren zwischen den Eigenschaften und Methoden, die zur Schnittstelle eines Objekts gehören, und denjenigen, die nicht zur Schnittstelle gehören. In Abbildung 4.19 ist dargestellt, wie die verschiedenen Sichtbarkeitsstufen in UML gekennzeichnet werden.

Abbildung 4.19    Darstellung der Sichtbarkeit in UML

In Abschnitt 5.1.6, »Sichtbarkeit im Rahmen der Vererbung«, kommt die Sichtbarkeitsstufe »Geschützt« hinzu, die die Sichtbarkeit mit Bezug auf die Vererbung der Implementierung regelt. Dort werden wir auch andere Sichtbarkeitsstufen, darunter die Stufe »Bereich«, beschreiben.

Sichtbarkeitsstufe »Öffentlich«

Bisher haben wir mit Bezug auf Objekte und Klassen immer nur von Operationen gesprochen, die über Methoden realisiert werden. Da eine Operation genau das ist, was ein Objekt nach außen zur Verfügung stellt, muss die entsprechende Methode immer als öffentlich deklariert sein.


Icon Hinweis Sichtbarkeitsstufe »Öffentlich« (public)

Alle Elemente (Methoden und Dateneinträge) eines Objekts, die zu seiner Schnittstelle gehören, haben die Sichtbarkeitsstufe »Öffentlich« (public). Eine öffentliche Methode kann jeder Benutzer des Objekts aufrufen, auf öffentliche Datenobjekte kann jeder Benutzer des Objekts zugreifen.


Warum überhaupt Sichtbarkeitsstufen?

Die Operationen, die wir auf einem Exemplar einer Klasse ausführen können, werden durch die Klasse selbst spezifiziert. Umgesetzt werden diese Operationen dann durch die Methoden des Objekts.

Aber nicht jede Methode muss gleich zur Schnittstelle eines Objekts gehören. Es ist gerade eine Stärke der Objektorientierung, dass ein Objekt nur einen Teil seiner Funktionalität nach außen zur Verfügung stellen muss. Ein Objekt kann also durchaus Methoden haben, die lediglich dazu dienen, eine komplexe Methode in kleinere, überschaubare Methoden aufzuspalten.

Manche Methoden sind nicht Teil der Schnittstelle.

Doch diese neuen Methoden gehören nicht zu der spezifizierten Schnittstelle der Exemplare, sie gehören nur zu ihrer technischen Umsetzung. Da wir die Schnittstelle von ihrer Umsetzung trennen und die Benutzer der Objekte von den Details ihrer Implementierung abschirmen möchten, müssen wir dafür sorgen, dass die Benutzer diese zusätzlichen Methoden nicht aufrufen können. Durch die Wahl der Sichtbarkeitsstufe »Privat« werden diese Methoden nach außen hin unsichtbar.

Sichtbarkeitsstufe »Privat«

Bei der Beschreibung der privaten Sichtbarkeit müssen wir zwischen dem klassen- und dem objektbasierten Sichtbarkeitskonzept unterscheiden. Das klassenbasierte Sichtbarkeitskonzept wird dabei zum Beispiel von Java, C# oder C++ verfolgt.


Icon Hinweis Sichtbarkeitsstufe »Privat« (klassenbasierte Sichtbarkeit)

Beim klassenbasierten Sichtbarkeitskonzept kann auf private Daten und Methoden eines Objekts nur aus den Methoden der Klasse zugegriffen werden, in der diese privaten Elemente deklariert worden sind.

Wir sind allerdings nicht nur auf das aufrufende Objekt beschränkt, sondern wir können auch auf private Elemente anderer Exemplare derselben Klasse zugreifen. Die Zugehörigkeit zur selben Klasse bestimmt also die Sichtbarkeit.


Listing 4.5 zeigt ein Beispiel in Java, in dem auf das Datenelement size eines anderen Exemplars der Klasse MyClass zugegriffen werden kann, obwohl dieses als private deklariert ist.

class MyClass implements Comparable<MyClass> { 
   private int size; // Private Variable size 
   public int compareTo(MyClass other) { 
      if (size < other.size) return –1; 
      if (size > other.size) return 1; 
      return 0; 
   } 
   ... 
}

Listing 4.5    Zugriff auf private Datenelemente

Die Methode compareTo der Exemplare der Klasse MyClass greift nicht nur auf das eigene private Datenelement size zu, nein, sie greift auch auf das private Datenelement size des Objekts other zu. Sie darf das, weil die Methode compareTo in derselben Klasse implementiert ist, in der das Datenelement size enthalten ist. Da in Java die Sichtbarkeitsregeln klassen- und nicht objektbasiert sind, spielt es keine Rolle, dass das Objekt other ein anderes sein kann als das Objekt, dessen Methode compareTo aufgerufen wurde. Die Sichtbarkeitsregeln werden hier zur Übersetzungszeit und nur anhand der Klassen ausgewertet.

Diskussion: Was heißt schon privat?

Gregor: Dann heißt privat in diesem Fall also nicht, dass die Methode nicht von außen auf einem Objekt aufgerufen werden kann?

Bernhard: Nein, jedes andere Exemplar der Klasse kann die Methode auf unserem Objekt aufrufen, sofern dies aus einer Methode der Klasse selbst heraus geschieht.

Gregor: Gibt es denn eine Möglichkeit, die Sichtbarkeit wirklich auf das aktuelle Objekt einzuschränken, also auch in Java oder C++ eine objektbasierte Sichtbarkeitsregel anzuwenden?

Bernhard: Nein, nicht mit den Mitteln der Programmiersprache. Auf der anderen Seite ist die bestehende Vorgehensweise zum Beispiel für Vergleichsoperationen auch sehr sinnvoll. Wäre die Sichtbarkeit weiter eingegrenzt, könnten wir den Zugriff auf jegliche vergleichsrelevante Information nicht als privat deklarieren.

Die Klassenzugehörigkeit muss allerdings bei der Regelung der Sichtbarkeit nicht unbedingt eine Rolle spielen. Im objektbasierten Sichtbarkeitskonzept ist alleine das Objekt für die Regelung der Sichtbarkeit zuständig. Das objektbasierte Sichtbarkeitskonzept ist zum Beispiel in Ruby umgesetzt.


Icon Hinweis Sichtbarkeitsstufe »Privat« (objektbasierte Sichtbarkeit)

Beim objektbasierten Sichtbarkeitskonzept ist der Zugriff auf private Daten und Methoden eines Objekts nur innerhalb von Methoden möglich, die auf dem Objekt selbst ausgeführt werden.

Damit ist es zum Beispiel nicht möglich, innerhalb einer Vergleichsoperation, bei der das zu vergleichende Objekt übergeben wurde, auf dessen private Daten und Operationen zuzugreifen. Dies gilt auch dann, wenn das andere Objekt zur selben Klasse gehört.


Betrachten wir die Auswirkungen des objektbasierten Sichtbarkeitskonzepts am Beispiel der Programmiersprache Ruby.

Private Methoden in Ruby

Ist in Ruby eine Methode privat, kann man sie nur ohne Angabe des Zielobjekts aufrufen. Wenn nämlich das Zielobjekt nicht angegeben ist, gilt der Aufruf immer dem Objekt, in dessen Methode sich das Programm gerade befindet. Ohne Angabe des Zielobjekts ist also das aufrufende Objekt selbst das aufgerufene Zielobjekt. Auf das gerade aktive Objekt kann man sich in Ruby mit dem Schlüsselwort self beziehen. Doch nicht einmal die Angabe self als Zielobjekt ist beim Aufruf einer privaten Methode zulässig.

Ist in Ruby die Methode test privat, ist ihr Aufruf in der Form test zulässig; der Aufruf self.test jedoch nicht.

class A 
private 
  def test 
    print "test A" 
  end 
public 
  def testParameter(x) 
    test # OK, ruft die Methode test dieses Objekts auf 
    x.test # Ob x.test aufgerufen werden darf, 
           # wird zur Laufzeit bestimmt. 
  end 
end
class B 
public 
  def test 
    print "test B" 
  end 
end 
 
a = A.new 
b = b.new 
 
a.test # Fehler, a.test ist privat 
b.test  # OK, b.test ist öffentlich 
a.testParameter(b) # OK, b.test ist öffentlich 
a.testParameter(a) # Fehler, a.test ist privat

Listing 4.6    Zugriff auf private Datenelemente in Ruby

Daten in Ruby sind privat.

In Ruby sind die Datenelemente immer privat. Ein Datenelement eines Objekts trägt in Ruby immer das Präfix @. Der Zugriff auf private Datenelemente der Objekte von außen kann über Lese- und Schreibmethoden ermöglicht werden.

Hier das zugehörige Beispiel:

class A 
  def x 
    return @x 
  end 
  def x=(value) 
    @x = value 
  end 
end

Eine äquivalente und kompaktere Schreibweise:

class A 
  attr :x 
end

Differenzierungsmöglichkeiten

Damit lässt das Konzept der objektbasierten Sichtbarkeit eine feinere Differenzierung zu als das klassenbasierte Konzept. Aber auch hier kann es natürlich Sinn machen, dass aus Methoden eines Objekts auf die Interna eines anderen Exemplars derselben Klasse zugegriffen werden kann. Am Beispiel der Vergleichsoperation haben wir gesehen, dass das wünschenswert sein kann.

Betrachten wir dazu wieder ein Beispiel in Ruby, das ja ein objektbasiertes Sichtbarkeitskonzept verfolgt. Dort bringt in diesem Fall die Sichtbarkeitsstufe »Geschützt« das gewünschte Ergebnis.

Sichtbarkeitsstufe »Geschützt« in Ruby

In Ruby kann ein Objekt auf seine eigenen privaten Elemente immer zugreifen, unabhängig davon, in welcher Klasse sie deklariert worden sind. Ein Objekt kann auch immer auf seine eigenen geschützten Elemente zugreifen. Der Unterschied zwischen den Sichtbarkeitsstufen »Privat« und »Geschützt« in Ruby besteht darin, dass ein Objekt auf geschützte Elemente von anderen Objekten zugreifen kann, solange diese zur selben Klasse gehören, in der die aufgerufene Methode deklariert wurde. In Abbildung 4.20 ist eine Beispielhierarchie von Klassen aufgeführt, die jeweils eine Methode x aufweisen. Die Sichtbarkeit der Methode ist in den einzelnen Klassen aber unterschiedlich definiert.

Abbildung 4.20    Sichtbarkeitsstufen für verschiedene Klassen und Operationen

In Listing 4.7 ist die Umsetzung der Klassenhierarchie und der Methoden in Ruby aufgeführt.

class A 
protected 
  def x 
    print "A.x" 
  end 
public 
  def test(o)   
    o.x 
  end 
end 
 
class B < A # B ist eine Unterklasse von A 
end 
class C < A # C ist auch eine Unterklasse von A 
protected 
  def x # C überschreibt x von A 
    print "C.x" 
  end 
end
class D # D ist keine Unterklasse von A 
protected 
  def x # deklariert aber selbst die Methode x 
    print "D.x" 
  end 
end

Listing 4.7    Verwendung der Sichtbarkeitsstufe »Geschützt« in Ruby

Dabei hat die Klasse A eine zusätzliche Methode test zugeordnet (Zeile ), die auf dem übergebenen Objekt die Operation x aufruft. Je nach Klassenzugehörigkeit des übergebenen Objekts ist der Aufruf von x erlaubt oder führt zu einem Fehler.

Verschiedene Aufrufe der Operation x sind in Listing 4.8 dargestellt.

a, b, c, d = A.new, B.new, C.new, D.new 
 
a.test(b) # OK, weil die Methode b.x in A definiert wurde 
b.test(a) # auch OK, weil b indirekt zu A gehört 
a.test(c) # Fehler, weil c.x nicht in A definiert wurde 
c.test(a) # OK, weil c indirekt zu A gehört 
a.test(d) # Fehler, d.x wurde nicht in A deklariert

Listing 4.8    Aufrufe von geschützten Methoden in Ruby

Weitere Sichtbarkeitsstufen

Die Behandlung der Sichtbarkeit ist vielfältig und unterscheidet sich von Programmiersprache zu Programmiersprache. Wir geben hier nur einen kurzen Überblick über weitere Varianten der Sichtbarkeit.

Java: Geschützt im Package

Ist in Java bei den Elementen einer Klasse keine Sichtbarkeit angegeben, dann ist das Element »Geschützt innerhalb des Packages«. Diese Stufe entspricht der Stufe »Geschützt«, allerdings ist das Element außerhalb des Packages nicht sichtbar.


C#: intern und intern geschützt

C# kennt noch die Sichtbarkeitsstufen: »Intern« (englisch internal) und »Intern geschützt« (englisch protected internal). Diese entsprechen den Stufen »Öffentlich« und »Geschützt«, beschränken die Sichtbarkeit allerdings auf das aktuelle Programmmodul (englisch Assembly)11  .


Sichtbarkeitsregeln gibt es nicht nur für die Elemente der Klassen, sondern auch für die Klassen selbst. Man kann zum Beispiel bestimmen, ob eine Klasse in der ganzen Anwendung sichtbar sein sollte oder nur innerhalb eines ihrer Teile. Auch hier unterscheiden sich die Sichtbarkeitsregeln von Programmiersprache zu Programmiersprache.


Rheinwerk Computing - Zum Seitenanfang

4.2.6 Klassenbezogene Methoden und Attribute  Zur nächsten ÜberschriftZur vorigen Überschrift

Bisher haben wir von Methoden und Datenelementen einzelner Objekte gesprochen. Manche Routinen und Daten lassen sich jedoch nicht einzelnen Objekten zuordnen.

Damit stellt sich die Frage: Wenn ein objektorientiertes Programm ausschließlich aus interagierenden Objekten besteht, wem können dann diese Routinen und Daten zugeordnet werden?

Hier bietet sich das zweite fundamentale Konstrukt der objektorientierten Programme an, nämlich die Klasse. Genau wie Objekte lassen sich auch Klassen Methoden und Daten zuordnen, die dann über den Aufruf von Operationen ausgeführt werden.

Sie werden in diesem Abschnitt Beispiele für Methoden und Daten kennen lernen, die direkt Klassen zugeordnet werden können.

Einsatz

Am weitesten verbreitet sind dabei die folgenden Verwendungen:

  • Konstruktoren erstellen neue Exemplare einer Klasse.
  • Verwaltungsinformation für Exemplare einer Klasse
  • Operationen auf primitiven Datentypen
  • Hilfsfunktionen
  • Klassenbezogene Konstanten

Im Folgenden stellen wir diese Anwendungen jeweils kurz vor.

Konstruktoren

Dass sich eine Operation nicht direkt einem Objekt zuordnen lässt, ist am offensichtlichsten bei Operationen, die ein komplett neues Objekt erstellen sollen. Wenn wir noch kein Objekt vorliegen haben, kann diese Operation auch nicht auf einem existierenden Objekt aufgerufen werden. Da mit dem Aufruf aber ein Exemplar einer ganz konkreten Klasse erstellt wird, können Konstruktoren als Basisoperationen der Klasse selbst aufgefasst werden.


Icon Hinweis Konstruktoren

Konstruktoren sind Operationen einer Klasse, durch die Exemplare dieser Klasse erstellt werden können. Die Klassendefinition dient dabei als Vorlage für das durch den Aufruf einer Konstruktoroperation erstellte Objekt.


Über Konstruktoren werden also Exemplare einer Klasse erzeugt. In den meisten Programmiersprachen wird der Name der entsprechenden Klasse auch für den Konstruktoraufruf verwendet. Im Beispiel von Listing 4.9 sind ein sehr einfacher Konstruktor und dessen Aufruf in der Programmiersprache Java dargestellt. Die Klasse Spaetzle hat einen gleichnamigen Konstruktor Spaetzle() zugeordnet. Durch den Aufruf von new Spaetzle() wird ein neues Exemplar der Klasse erstellt. Der Konstruktor ist also eine besondere Operation, die von der Klasse zur Verfügung gestellt wird.

public class Spaetzle { 
    Spaetzle() 
    { 
    }; 
    ... 
} 
 
Spaetzle portion = new Spaetzle();

Listing 4.9    Einfaches Beispiel für einen Konstruktoraufruf

Konstruktoren sind nicht die einzige Möglichkeit, neue Exemplare einer Klasse zu erzeugen. Weitere Möglichkeiten werden Sie in Abschnitt 7.2, »Fabriken als Abstraktionsebene zur Objekterzeugung«, kennen lernen.

Verwaltungsinformation für Exemplare einer Klasse

Ein Beispiel für eine sinnvolle Verwendung der klassenbasierten Elemente ist eine Registratur für Exemplare einer Klasse. So könnte die Klasse ProtocolHandler spezifizieren, dass jedes ihrer Exemplare eine Methode canHandle besitzt, mit der es die Frage beantworten kann, ob es ein Protokoll bearbeiten kann. Diese verschiedenen Exemplare würden sich dann in die klassenbezogene Registratur eintragen, so dass eine klassenbezogene Methode getProtocolHandler das richtige Exemplar für das jeweilige Protokoll aussuchen kann.

Zur Verwaltung der Exemplare, die sich dort eingetragen haben, benötigt die Klasse auch eine klassenbezogene Datenstruktur, also zum Beispiel eine Liste. Mehr zu diesem Beispiel finden Sie in Abschnitt 7.2, »Fabriken als Abstraktionsebene zur Objekterzeugung«.

Operationen auf primitiven Datentypen

Für Operationen und Daten, die sich auf primitive Datentypen beziehen, bietet es sich an, diese als klassenbezogene Methoden zu implementieren. Primitive Datentypen sind in vielen Programmiersprachen keine Objekte und können deshalb auch keine eigenen Methoden haben.

In Java oder auch Ruby gibt es zum Beispiel die Klasse Math, welche die gängigsten mathematischen Konstanten wie die Zahl p und Funktionen wie Sinus oder Kosinus als klassenbasierte Elemente enthält.

In Java sind die Datentypen int oder float primitiv, ihre Exemplare sind keine Objekte und können keine eigenen Methoden haben. In Ruby dagegen ist alles ein Objekt, also auch die Klassen. Damit sind alle Klassen erweiterbar. So können Sie zum Beispiel die Klasse Float um die Methode sin erweitern, wie in Listing 4.10 dargestellt.

class Float 
  def sin 
    Math::sin(self) 
  end 
end 
 
2.0.sin # gibt 0.909297426825682 aus

Listing 4.10    Erweiterung der Klasse »Float« in Ruby

Hilfsfunktionen als klassenbezogene Methoden

Eine häufige Verwendung finden klassenbezogene Methoden und Dateneinträge als eine Art Organisationsform für bestimmte Hilfs- oder Bibliotheksmethoden, die mit Exemplaren von anderen Klassen arbeiten, aber nicht unbedingt zu dem Umfang der Spezifikation dieser Klassen gehören.

So gehört es zum Beispiel nicht zur Spezifikation der Klasse String in Java, dass ihre Exemplare überprüfen können, ob sie eine Zahl repräsentieren und den Wert dieser Zahl zurückgeben. Diese nützliche Funktionalität ist zum Beispiel als eine klassenbezogene Methode parseFloat der Klasse Float implementiert:

... 
Float ungefaehrPi = Float.parseFloat("3.1415"); 
...

In diesem Fall wird also eine Operation auf der Klasse Float selbst aufgerufen und ein Exemplar der Klasse zurückgeliefert. Hier haben wir also bereits ein weiteres Beispiel gesehen, wie sich neben Konstruktoren über andere klassenbezogene Operationen Exemplare einer Klasse erstellen lassen.

Klassenbezogene Konstanten

Konstanten, die bei der Ausführung von Operationen benötigt werden, können als klassenbezogene Konstanten definiert werden. Sie werden damit zu nicht änderbaren Daten der Klasse.

Zum Beispiel können Sie der Klasse Spaetzle aus unserem Beispiel in Listing 4.9 eine Konstante OPTIMALE_ANZAHL_EIER_PRO_PERSON hinzufügen. Diese in Schwaben generell anerkannte Konstante hat den Wert 2.5 und kann dann in den entsprechenden Operationen auch von außen verwendet werden.

public class Spaetzle { 
  static final double OPTIMALE_ANZAHL_EIER_PRO_PERSON = 2.5;

Diese Konstante kann nun in Berechnungen von weiteren Mengenvorgaben als Standardwert verwendet werden. [Spätzle dienen hier nur als Beispiel, deshalb wird das komplette Rezept hier und in den folgenden Source-Code-Beispielen nicht auftauchen. Wenn Sie daran interessiert sind, erhalten Sie es auf der Webseite zum Buch (www.objektorientierte-programmierung.de). ]

Umsetzung von klassenbezogenen Methoden und Daten in den Programmiersprachen

Programmiersprachen bieten verschiedene Möglichkeiten, klassenbezogene Methoden und Daten einzusetzen.

static ist das Schlüsselwort.

In C++, C# oder Java werden sie mit dem Schlüsselwort static gekennzeichnet. In Ruby benutzt man statt eines Schlüsselwortes den Namen der Klasse selbst als Präfix der klassenbezogenen Methode. Klassenbezogene Dateneinträge enthalten in Ruby das Präfix @@ statt das Präfix @ der objektbezogenen Dateneinträge.

Sie könnten die klassenbezogenen Methoden und Daten, zumindest in bestimmten Programmiersprachen wie zum Beispiel C++, auch als globale Prozeduren, Funktionen und Variablen implementieren. Klassenbezogene Elemente haben jedoch den Vorteil, dass Sie Sichtbarkeitsregeln dafür deklarieren können. Die in Abschnitt 4.2.5 beschriebenen Sichtbarkeitsregeln lassen sich auch auf klassenbezogene Methoden und Daten anwenden. Damit trägt dieses Vorgehen zur Übersichtlichkeit Ihrer Programme bei.

Globale Methoden in Java und C#

In Java oder C# gibt es überhaupt keine globalen Methoden oder Dateneinträge, daher bieten hier die klassenbezogenen Elemente die einzige Möglichkeit, Routinen, die nicht einem Objekt zugeordnet sind, zu implementieren.


Rheinwerk Computing - Zum Seitenanfang

4.2.7 Singleton-Methoden: Methoden für einzelne Objekte  topZur vorigen Überschrift

In den klassenbasierten Programmiersprachen wie Java, C#, C++, Ruby oder Python definieren wir Methoden in der Regel immer für alle Exemplare einer Klasse. Damit sind auf alle Exemplare einer Klasse dieselben Operationen anwendbar.

Aber nicht alle Programmiersprachen erzwingen diese Gleichförmigkeit. In einigen dynamisch typisierten Sprachen ist es durchaus auch möglich, Methoden einzelnen Objekten zuzuordnen. Der Namensgebung der Programmiersprache Ruby folgend, werden diese Methoden Singleton-Methoden genannt. [Singleton-Methoden dürfen nicht mit den Singleton-Objekten verwechselt werden, die Sie in Abschnitt 7.2.6, »Erzeugung von Objekten als Singletons«, kennen lernen werden. ]


Icon Hinweis Singleton-Methoden

Singleton-Methoden sind Methoden, die genau einem Objekt zugeordnet sind. Eine Singleton-Methode wird nach der Erstellung einem konkreten Objekt hinzugefügt. Danach unterstützt das Objekt die durch die Methode realisierte Operation.


Betrachten wir die Umsetzung einer Singleton-Methode an einem Beispiel in der Programmiersprache Ruby. Das Beispiel zeigt, dass die Methode test in diesem Fall nur an genau einem Objekt vorhanden ist.

class A 
end 
 
a1 = A.new 
a2 = A.new 
 
def a1.test 
  return "Test" 
end 
 
a1.test # OK, das Objekt a1 hat die Methode test 
a2.test # Fehler, das Objekt a2 hat keine Methode test

Listing 4.11    Singleton-Methoden in Ruby

In C++, Java oder C# gibt es keine Möglichkeit, eine Methode für ein einzelnes Objekt zu implementieren. In Java können Sie aber einen ähnlichen Effekt mit den anonymen Klassen erreichen, die in Abschnitt 5.2.3 beschrieben werden.



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

 <<   zurück
  Zum Rheinwerk-Shop
Neuauflage: Objektorientierte Programmierung






Neuauflage:
Objektorientierte Programmierung

Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Rheinwerk-Shop: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Rheinwerk-Shop: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Rheinwerk-Shop: C++ Handbuch






 C++ Handbuch


Zum Rheinwerk-Shop: Einstieg in Python






 Einstieg in Python


Zum Rheinwerk-Shop: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2009
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.


Nutzungsbestimmungen | Datenschutz | Impressum

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

Cookie-Einstellungen ändern