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.4 Klassen von Werten und Klassen von Objekten  Zur nächsten ÜberschriftZur vorigen Überschrift

Objekte haben Identität.

In objektorientierten Systemen unterscheiden wir zwischen Objekten und Werten. Ein Objekt hat seine Identität und kann sich im Laufe seiner Existenz verändern. Es kann verschiedene Objekte (mit unterschiedlicher Identität) geben, die zu einem Zeitpunkt fachlich gesehen gleich sind, sich aber zu einem späteren Zeitpunkt unterscheiden.

Wenn wir eine Datei kopieren, hat die ursprüngliche Datei den gleichen Inhalt wie ihre Kopie. Die Originaldatei und ihre Kopie haben aber unterschiedliche Identitäten. Später, nachdem man eine der Dateien bearbeitet hat, sind es immer noch jeweils dieselben Dateien mit ihren Identitäten, sie sind aber nicht mehr gleich.

Werte haben keine eigene Identität.

Werte dagegen haben keine Identität, und sie können sich nicht ändern. Eine 3 bleibt immer eine 3, und man kann sie nicht sinnvoll von einer in einem anderen Speicherbereich gespeicherten 3 unterscheiden.

Im Folgenden werden wir zunächst die Behandlung von Werten in Programmiersprachen vorstellen und das Entwurfsmuster »Fliegengewicht« diskutieren. Dieses Entwurfsmuster erlaubt eine effiziente Verwaltung von Objekten, die in Teilen die Eigenschaften von Werten haben.


Rheinwerk Computing - Zum Seitenanfang

4.4.1 Werte in den objektorientierten Programmiersprachen  Zur nächsten ÜberschriftZur vorigen Überschrift

Manche objektorientierte Programmiersprachen unterscheiden zwischen Werten und Objekten und bringen eine kleine Auswahl von primitiven Wertetypen mit. So gehören in Java, C# oder C++ die Typen int, float oder char zu den Bestandteilen der Programmiersprache.

Häufig, ja fast immer, brauchen wir in Anwendungen, die wir entwickeln, auch andere Wertetypen. So können wir in einer mathematischen Anwendung einen Wertetyp für komplexe Zahlen oder für Matrizen brauchen und in fast jeder Anwendung einen Datentyp für eine Zeichenkette oder das Datum. Für jeden dieser Wertetypen gelten besondere Regeln, und man kann sie in verschiedenen Operationen verwenden, die ausprogrammiert werden müssen.

Solche komplexen Werte haben also – ähnlich wie Objekte – ihre eigenen Datenstrukturen und ihre eigene Funktionalität. In der Umsetzung unterscheiden sie sich also prinzipiell nicht von den Objekten.

Werte als Objekte implementiert

Daher werden in den objektorientierten Programmiersprachen auch Werte als Objekte implementiert. Ob es sich bei einem Objekt um einen Wert oder um ein Objekt mit einer eigenen fachlichen Identität handelt, ist also eine konzeptionelle Entscheidung des Entwicklers. Die Implementierung ist die gleiche. Daher haben in den objektorientierten Programmiersprachen auch die Werte eine technische Identität und eine Lebensdauer. [Ausnahmen in einigen Sprachen sind primitive Wertetypen wie int oder float. ] Wir sprechen deshalb in diesem Fall von Wertobjekten (engl. Value Objects). Diese Wertobjekte werden jedoch so umgesetzt, dass die ihnen zugeordneten Daten nicht änderbar sind, nachdem das Objekt einmal angelegt wurde. [Im Rahmen der J2EE-Architektur wird der Begriff Value Object (Wertobjekt) hin und wieder in einer anderen Bedeutung gebraucht. Dort wird eine Datenstruktur damit bezeichnet, die für den Datenaustausch zwischen Server und Client verwendet wird. Mittlerweile hat sich dafür im J2EE-Sprachgebrauch aber der Begriff Data Transfer Object etabliert. ]


Icon Hinweis Wertobjekte (Value Objects)

Wertobjekte sind Objekte, deren Eigenschaften nach der Konstruktion nicht mehr verändert werden können. Wird eine Änderung an einem Wertobjekt benötigt, so wird nicht das Objekt selbst geändert, sondern eine geänderte Kopie des Objekts verwendet.21 


In Abbildung 4.32 ist die Modellierung eines Datums als Wertobjekt dargestellt. Die Eigenschaften des Objekts sind dabei nicht änderbar. Sie sind mit der Eigenschaft {frozen} markiert. So kann jedes Datumsobjekt von mehreren Konferenzobjekten referenziert werden. Wird der Termin einer Konferenz verschoben, wird diese mit einem anderen Datum assoziiert.

Abbildung 4.32    Datum als Wertobjekt

Ein weiteres Beispiel für eine Klasse solcher Wertobjekte ist die Klasse String in Java. Strings können nach ihrer Konstruktion nicht mehr verändert werden, alle ändernden Operationen liefern neue Exemplare der Klasse String.

Die Unterscheidung zwischen einem Wert und einem Objekt mit eigener Identität und Lebenszeit ist fachlich und auch technisch relevant.

Wenn Sie mit Wertobjekten arbeiten, sollten Sie immer daran denken, dass Sie nicht deren Identität, sondern deren fachliche Gleichheit überprüfen müssen. So finden zum Beispiel zwei Ereignisse zum selben Zeitpunkt statt, wenn die Objekte, in denen der Zeitpunkt gespeichert wird, den gleichen Inhalt haben, auch wenn es sich dabei um zwei verschiedene Zeitpunktobjekte handelt. In Abbildung 4.33 sind die Auswirkungen dargestellt.

Abbildung 4.33    Datum als normales Objekt

In dem abgebildeten Design ist das Datum kein Wertobjekt, sondern ein Objekt mit eigener Identität und änderbaren Attributen. Daher muss mit jeder Konferenz ein eigenes Datumsobjekt assoziiert werden. Wenn eine Konferenz verschoben wird, müssen nur die Attribute des Datums geändert werden.

Geringer Speicherbedarf für Werte

Vor allem in Programmiersprachen mit automatischer Speicherverwaltung kann durch die Verwendung von Wertobjekten der Speicher effizienter genutzt werden. Da sich ein Wertobjekt nicht ändern kann, können mehrere andere Objekte sich auf dasselbe Wertobjekt beziehen, und man braucht sie nicht zu kopieren.

Nehmen Sie an, Sie haben das Datum als ein nicht änderbares Wertobjekt implementiert. Die Klasse Besprechung besitzt ein Datum, zu dem die Besprechung stattfindet. Nun können sich mehrere Besprechungen auf dasselbe Wertobjekt für das Datum beziehen, da mehrere Besprechungen am selben Tag (auch in unterschiedlichen Räumen) stattfinden können.

Wird jetzt eine Besprechung verschoben, wird sie sich auf ein neues, auch nicht änderbares Datumswertobjekt beziehen. Wenn alle Besprechungen, die sich auf das ursprüngliche Datumswertobjekt bezogen haben, verschoben oder gelöscht werden, kann auch das aktuell nicht mehr benötigte Datumswertobjekt gelöscht werden. Wenn Ihre Programmiersprache eine automatische Speicherverwaltung für Objekte unterstützt, dann wird das automatisch passieren. Das genaue Verfahren dabei beschreiben wir in Abschnitt 7.3.2, »Was ist eine Garbage Collection?«.

Wenn Sie nun aber die andere Modellierungsvariante aus Abbildung 4.33 wählen, in der ein Datum ein änderbares Objekt ist, müssen Sie auch andere Teile der Anwendung anpassen. Sie können in diesem Fall das Datumsobjekt zwar direkt ändern und einen Termin damit auf ein anderes Datum verschieben. Damit möchten Sie natürlich nicht gleich alle anderen Besprechungen, die zufällig am gleichen Tag stattfinden, mitverschieben. Wenn Sie die Modellierung aus Abbildung 4.32 gewählt hätten und dort das Datumsobjekt ändern würden, würde genau das passieren.

Da Sie jede Besprechung unabhängig von den anderen Besprechungen verschieben wollen, müssen Sie in diesem Fall die Modellierung von Abbildung 4.33 wählen, in der jeder Termin sein eigenes Datumsobjekt zugeordnet hat.

Diskussion: Änderungen an Wertobjekten

Bernhard: Sind denn Wertobjekte grundsätzlich nicht änderbar? Ich könnte mir vorstellen, dass es in einigen Fällen ganz sinnvoll sein kann, auch ein Wertobjekt zu ändern.

Gregor: Die Definition eines Wertobjekts sagt aus, dass diese nicht änderbar sind. Natürlich können wir uns dafür entscheiden, einen bestimmten Wert änderbar zu machen. Dann haben wir aber kein Wertobjekt mehr vorliegen, sondern eben ein änderbares Objekt.

In manchen Fällen ist es durchaus auch sinnvoll, bestimmte Teile von Objekten als unveränderlich zu betrachten und nur einen kleinen Teil als wirklich veränderlich. Die auf dieser Basis möglichen Optimierungen wollen wir nun beschreiben.


Rheinwerk Computing - Zum Seitenanfang

4.4.2 Entwurfsmuster »Fliegengewicht«  Zur nächsten ÜberschriftZur vorigen Überschrift

Gerade haben Sie gesehen, für welche Anwendungsbereiche Wertobjekte sinnvoll sein können. Dabei haben wir auch erwähnt, dass sich die Tatsache, dass Wertobjekte nicht änderbar sind, zur Optimierung des Speicherverbrauchs ausnutzen lässt.

Dadurch, dass Wertobjekte, die den gleichen Wert enthalten, nur einmal im Speicher existieren, ist oft eine relevante Reduktion des Speicherbedarfs zu erreichen. Die speicherschonende Mehrfachverwendung der Wertobjekte ist also eine sinnvolle Ausnutzung der Tatsache, dass Wertobjekte nicht änderbar sind.

Die Anwendung dieses Verfahrens ist aber nicht auf Wertobjekte beschränkt. Das Entwurfsmuster Fliegengewicht (engl. Flyweight) beschreibt eine Optimierung, die auf der gleichen Überlegung beruht.


Icon Hinweis Entwurfsmuster »Fliegengewicht« (engl. Flyweight)

Wenn sehr viele große, in weiten Teilen übereinstimmende Objekte verwendet werden, können wir das Entwurfsmuster »Fliegengewicht« einsetzen, um den Speicherbedarf zu minimieren. Bei der Verwendung dieses Entwurfsmusters teilt man die häufig verwendeten Objekte in zwei Teile auf.

Der leichte Teil enthält die identischen Informationen aller Objekte und kann mehrfach verwendet werden. Diesen Teil bezeichnen wir als Fliegengewicht.

Der schwere Teil enthält die Informationen, in denen sich die Objekte unterscheiden. Statt viele nahezu gleiche Objekte zu verwenden, verwendet man nun die leichten Fliegengewichte und reichert diese mit der zusätzlich benötigten Information an. Diese Information wird in der Regel den Objekten bei der Verwendung über einen Kontext übergeben.


In Abbildung 4.34 ist die grundlegende Struktur des Entwurfsmusters dargestellt.

Abbildung 4.34    Struktur des Entwurfsmusters »Fliegengewicht«

Ein Beispiel für die Anwendung des Musters ist ein Textbearbeitungsprogramm: In einem Text werden sehr viele Buchstaben verwendet. Jeder Buchstabe hat eine Farbe, eine Größe einen Umriss und so weiter. Der Umriss aller Buchstaben »a« einer Schriftart ist dabei immer der gleiche, wenn er auch für verschiedene Schriftgrößen skaliert werden muss. In Abbildung 4.35 sind die resultierenden Beziehungen dargestellt.

Abbildung 4.35    Anwendung auf Darstellung von Zeichen

Wenn man das Muster »Fliegengewicht« einsetzt, trennt man die Informationen über die Buchstaben in zwei Teile auf. Es gehört zur Kontextinformation, welche Farbe und Größe der Buchstabe hat.

Der Umriss der Buchstaben wird allerdings in den mehrfach verwendeten Fliegengewichtteilen gehalten. Pro Schriftart und Buchstaben gibt es also nur ein Fliegengewicht-Objekt. Dieses greift aber bei seiner Darstellung auf Informationen zurück, die ihm von außen übergeben werden. Eine Vorstellung des Entwurfsmusters findet sich auch in [Entwurfsmuster 2004].

Neben der erwarteten Speicherersparnis [Ob man durch die Mehrfachverwendung eines Wertobjekts wirklich Speicher spart, hängt von der Größe solcher Wertobjekte und der Anzahl deren Besitzer ab. Denn zusätzlich zu dem Objekt selbst muss jeder Besitzer einen Zeiger bzw. eine Referenz auf das Objekt speichern, und die automatische Speicherverwaltung hat auch ihren Preis. ] hat die Mehrfachverwendung noch einen weiteren Vorteil, der beim Vergleich von Objekten zum Tragen kommt. Wenn sich zwei Objekte auf dieselbe Zeichenkette beziehen, ist sofort klar, dass die Zeichenkette mit sich selbst auch gleich ist. Vergleichen wir dagegen zwei Zeichenketten mit unterschiedlichen technischen Identitäten auf ihre Gleichheit, müssen wir ihre Zeichen so lange einzeln vergleichen, bis wir einen Unterschied finden.

Im folgenden Abschnitt werden wir eine spezielle Art von Werten betrachten: die Aufzählungen oder Enumerations. Wir werden dabei die verschiedenen Möglichkeiten vorstellen, solche Werte als Objekte zu betrachten.


Rheinwerk Computing - Zum Seitenanfang

4.4.3 Aufzählungen (Enumerations)  Zur nächsten ÜberschriftZur vorigen Überschrift

Die Exemplare mancher Klassen sind fachlich vorgegeben und bereits bei der Erstellung der Software bekannt. Sie können während der Verwendung der Software aus fachlichen Gründen weder erstellt noch gelöscht werden. In einer Anwendung werden Sie zum Beispiel neue Kunden eingeben können und Kunden eventuell aus dem Kundenstamm löschen. Dies werden Sie aber kaum mit den Himmelsrichtungen Norden, Süden, Osten und Westen, den Wochentagen, den Monatsnamen oder den Farben der Spielfiguren beim Schach machen.

Aufzählungen als spezielle Klassen

Aufzählungen sind keine Spezialität der Objektorientierung. Die meisten Programmiersprachen kennen ein Konstrukt, um einem Datentyp eine endliche Menge von aufzählbaren Werten zuzuordnen. Allerdings lassen sich die zugehörigen Werte im Rahmen der Objektorientierung als Objekte mit zugehörigen Methoden umsetzen. Wir können damit Aufzählungen als spezielle Klassen betrachten, von denen eine genau definierte Menge von Exemplaren existiert.

Aufzählungen als abgegrenzte Mengen von Objekten

In Abbildung 4.36 ist die Beziehung zwischen den Wochentagen und der zugehörigen Klasse aufgezeichnet. Es gibt genau sieben Exemplare der Klasse Wochentag, die in unserem Modell jeweils eine spezifische Abkürzung zugeordnet haben, die sich über die Operation abkuerzung() erfragen lässt. Außerdem gibt es eine Methode istWochenende, mit der sich ein Objekt befragen lässt, ob es für einen Wochenendtag steht.

In Java ab der Version 5 lässt sich diese Modellierung direkt auf ein Sprachkonstrukt abbilden. Mit dem Konstrukt enum bietet Java die Möglichkeit, eine Klasse speziell für die Aufzählung von Objekten zu deklarieren, wobei die Objekte auch eigene Daten und Methoden haben können. In Listing 4.13 ist der entsprechende Quelltext für Java 5 dargestellt.

Abbildung 4.36    Objekte, die Wochentage repräsentieren

enum Wochentag { 
    MONTAG("Mo"), DIENSTAG("Di"), MITTWOCH("Mi"), 
    DONNERSTAG("Do"), FREITAG("Fr"), SAMSTAG("Sa"), 
    SONNTAG("So"); 
 
    private final String abkuerzung; 
 
    Wochentag(String abkuerzung) { 
       this.abkuerzung = abkuerzung; 
    } 
 
    public boolean istWochenende() { 
        return this == SAMSTAG || this == SONNTAG 
    } 
 
    public String abkuerzung() { 
        return abkuerzung; 
    } 
}

Listing 4.13    Aufzählung von Wochentagen in Java 5

Elemente einer Aufzählung mit Methoden

Dabei können sogar die einzelnen Einträge einer Aufzählung eigene Methoden haben und die Methoden der Klasse überschreiben. Hier eine andere Implementierung der Aufzählung Wochentag, die das verdeutlicht. Die Objekte, die Samstag und Sonntag repräsentieren, haben die Methode istWochenende überschrieben und werden sich damit auf Nachfrage selbst als Wochenendtage einordnen.

enum Wochentag { 
    MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG, FREITAG, 
    SAMSTAG { 
        @Override boolean istWochenende() { 
            return true; 
        } 
    }, 
    SONNTAG { 
        @Override boolean istWochenende() { 
            return true; 
        } 
    }; 
    boolean istWochenende() { 
        return false; 
    } 
}

Listing 4.14    Samstag und Sonntag mit eigenen Methoden

Aufzählungen als typsichere Menge von numerischen Werten

Allerdings bieten nicht alle Programmiersprachen eine solche Objektsicht auf die Elemente von Aufzählungen. Die andere Sichtweise auf Aufzählungen ist es, einfach eine typsichere Verwendung von numerischen Werten zur Verfügung zu stellen. Operationen auf diesen Werten können dann in einer eigens dafür eingeführten Klasse realisiert werden. In Abbildung 4.37 ist diese Variante für das Beispiel zu den Wochentagen dargestellt. Die einzelnen Wochentage werden durch Zahlenwerte repräsentiert, Informationen über die einzelnen Tage werden über klassenbezogene Methoden der Klasse Woche ausgewertet.

Abbildung 4.37    Aufzählungen für typsicheren Zugriff

Eine Umsetzung dieses Verfahrens in C# ist in Listing 4.15 dargestellt. Das gleiche Vorgehen wäre auch in der Sprache C++ möglich.

  enum Wochentag { 
    MONTAG = 1, 
    DIENSTAG = 2, 
    MITTWOCH = 4, 
    DONNERSTAG = 8, 
    FREITAG = 16, 
    SAMSTAG = 32, 
    SONNTAG = 64, 
  } 
  class Woche { 
    private static Wochentag WOCHENENDE = 
      Wochentag.SAMSTAG | Wochentag.SONNTAG; 
    public static bool istWochenende(Wochentag tag) { 
      return 0 != (tag & WOCHENENDE); 
    } 
  }

Listing 4.15    Aufzählung von Wochentagen in C#

Aufzählungen als typsichere Zahlen in C#

Wie Sie sehen, sind Aufzählungen in C# keine echten Klassen mit eigenen Daten und Methoden, es sind einfach nur speziell bezeichnete und typsicher gemachte ganze Zahlen. Werte, die keine eigene Identität haben. Die Vorteile dieser Vorgehensweise sind:

  • Sie müssen sich keine Gedanken über die Gleichheit oder Identität beim Vergleichen machen.
  • Sie können, wie in unserem Beispiel, kombinierte Werte einer Aufzählung als Bits einer Bitmenge in einer Variablen speichern.
  • Das Speichern in einer Datei und das Wiederauslesen ist einfach – das Vorgehen entspricht der Behandlung der ganzen Zahlen.

Aufzählungen in Java vor der Version 5

Sie haben in den vorausgehenden Abschnitten gesehen, dass Java ab der Version 5 eine Objektsicht auf Aufzählungen erlaubt.

Auch in den Versionen vor Java 5 lassen sich typsichere Aufzählungen von Objekten realisieren. Allerdings müssen Sie dafür mehr Code schreiben als in der sehr kompakten Darstellung der Version 5. In Abbildung 4.38 ist die resultierende Klasse mit ihren Daten und Operationen aufgeführt.

Dabei ordnen Sie jedem Exemplar von Wochentag einen internen, zum Beispiel ganzzahligen Wert zu, der beim Serialisieren und beim Vergleichen verwendet wird. Die einzelnen Wochentage wiederum ordnen Sie als klassenbezogene Daten der Klasse Wochentag zu. Jedem Wochentag ist ein eigener ganzzahliger Wert (value) zugeordnet, der für Vergleiche herangezogen werden kann. Listing 4.16 zeigt die Umsetzung der Klasse Wochentag.

Abbildung 4.38    Klasse für Wochentage in Java bis Version 4

Serialisierbarer Wochentag

final class Wochentag implements Serializable {  
    private static final long 
        serialVersionUID = 3544667382522852404L; 
 
    private static int COUNTER = 0; 
 
    private final int value; 
 
    public static final Wochentag MONTAG =      
        new Wochentag(++COUNTER); 
    public static final Wochentag DIENSTAG = 
        new Wochentag(++COUNTER); 
    public static final Wochentag MITTWOCH = 
        new Wochentag(++COUNTER); 
    public static final Wochentag DONNERSTAG = 
        new Wochentag(++COUNTER); 
    public static final Wochentag FREITAG = 
        new Wochentag(++COUNTER); 
    public static final Wochentag SAMSTAG = 
        new Wochentag(++COUNTER); 
    public static final Wochentag SONNTAG = 
        new Wochentag(++COUNTER); 
    public static final Wochentag[] VALUES = 
        new Wochentag[] {MONTAG, DIENSTAG, MITTWOCH, 
              DONNERSTAG, FREITAG, SAMSTAG, SONNTAG}; 
 
    private Wochentag(int value) {  
        this.value = value; 
    } 
 
    public boolean istWochenende() { 
        return equals(SAMSTAG) || equals(SONNTAG); 
    } 
 
    public boolean equals(Object o) {  
        if (!(o instanceof Wochentag)) return false; 
        return value == ((Wochentag)o).value; 
    } 
 
    public int hashCode() {  
        return value; 
    } 
    private Object readResolve() 
            throws ObjectStreamException { 
        for (int i = 0; i < VALUES.length; ++i) { 
            if (equals(VALUES[i])) return VALUES[i]; 
        } 
        throw new 
            InvalidObjectException("Unbekannter Wochentag"); 
    } 
}

Listing 4.16    Typsichere Umsetzung von Wochentag-Objekten in Java bis Version 5

Die Klasse Wochentag in Zeile ist hier eine ganz gewöhnliche Klasse in Java, sie hat allerdings einen privaten Konstruktor , so dass Exemplare dieser Klasse nur innerhalb des Quelltextes der Klasse selbst erstellt werden können. Dadurch soll sichergestellt werden, dass es nur die genau aufgezählten sieben Exemplare (ab Zeile ) gibt und niemand auf einmal neue Wochentage erfindet.

Durch die Umsetzung der Operationen equals und hashCode in den Zeilen und stellt die Implementierung sicher, dass auch über verschiedene Classloader ins System gelangte Wochentage korrekt als gleich erkannt werden, dass sie den gleichen zugeordneten numerischen Wert haben. Durch die Umsetzung der Operation readResolve in Zeile wird außerdem sichergestellt, dass bei einem erneuten Einlesen nach einer Serialisierung keine neuen Objekte angelegt, sondern die bereits angelegten Objekte verwendet werden.

Mit der beschriebenen Implementierung haben Sie also eine typsichere und serialisierbare Aufzählung von echten Objekten vorliegen, die eigene Daten, Operationen, Beziehungen und Methoden haben können. Allerdings sieht das nach ziemlich viel Code für so etwas Einfaches wie eine Aufzählung von Wochentagen aus. Und es gibt wirklich auch Lösungen, die scheinbar einfacher sind, nur leider nicht korrekt arbeiten.

Werfen Sie deshalb noch einen kurzen Blick auf zwei weitere Varianten der Umsetzung und die Gründe, warum diese nicht empfehlenswert sind.

Fehleranfällige Variante 1: Klassenkonstanten

In Java gab es bis zur Version 5 kein spezielles Konstrukt für Aufzählungen. Stattdessen definierte man eine Reihe von Klassenkonstanten (in Java-Terminologie »finale statische Variable«). Je nach Bedarfsfall war der Typ der Variablen entweder eine ganze Zahl oder eine typisierte Objektreferenz.

Wenn Sie ganze Zahlen als Typ der Aufzählung verwenden, haben Sie die Vorteile, die auch C# oder C++ bieten: Der Vergleich ist einfacher, weil Sie nicht zwischen der Gleichheit und der Identität unterscheiden müssen, die Serialisierung ist trivial, Sie können kombinierte Werte in einer Variablen speichern. Diese Vorgehensweise wird zum Beispiel häufig bei den Konstanten in den Benutzeroberflächenbibliotheken AWT und Swing verwendet.

Doch im Vergleich zu C# oder C++ hat diese Vorgehensweise einen entscheidenden Nachteil: Sie ist nicht typsicher. In Java sind es eben nur speziell benannte ganze Zahlen. So können Sie in Java den Stil einer Schriftart irrtümlich deren Größe zuordnen, ohne dass der Compiler dieses Missgeschick bemerkt.

Fehleranfällige Variante 2: Objekte statt Konstanten

Um die Typsicherheit der Aufzählung zu erreichen, können Sie nun statt ganzer Zahlen besser Exemplare von Klassen verwenden, die spezifisch für jede Aufzählung definiert werden.

final class Wochentag { 
  public static final Wochentag MONTAG = new Wochentag(); 
  public static final Wochentag DIENSTAG = new Wochentag(); 
  public static final Wochentag MITTWOCH = new Wochentag(); 
  public static final Wochentag DONNERSTAG = new Wochentag(); 
  public static final Wochentag FREITAG = new Wochentag(); 
  public static final Wochentag SAMSTAG = new Wochentag(); 
  public static final Wochentag SONNTAG = new Wochentag(); 
 
  private Wochentag() {} 
 
  public boolean istWochenende() { 
      return this == SAMSTAG || this == SONNTAG; 
  } 
}

Listing 4.17    Exemplare der Klasse Wochentag

Problem 1: Deserialisierung

Das Problem mit dieser Implementierung ist, dass sie in komplexeren Situationen nicht funktioniert. Die Exemplare der Klasse lassen sich zwar leicht serialisierbar machen (indem die Klasse die leere Schnittstelle Serializable zu implementieren angibt), die wieder deserialisierten Objekte werden aber neu erstellt und nicht durch die bestehenden sieben ersetzt. Sie erhalten plötzlich neue Wochentage – und das Schlimmste daran ist, so wie wir es implementiert haben, werden diese neuen Wochentage nicht zum Wochenende hinzugerechnet.

Problem 2: Classloader

Ein anderes Problem besteht darin, dass in einer komplexen Java-Anwendung mehrere Classloader aktiv sein können und jeder dieser Classloader die Klasse Wochentag erneut laden kann. So können wir plötzlich zwei Sonntage haben, die nicht identisch sind. Dies kann schnell zu einem fehlerhaften Verhalten unserer Anwendung führen, wenn der Vergleich von zwei Wochentag-Objekten, die beide für den Sonntag stehen, fehlschlägt.

Aufgrund dieser beiden Probleme landen wir dann wieder bei der Implementierung aus Listing 4.16, die gerade mit Serialisierung und Deserialisierung umgehen kann und Vergleiche auch dann korrekt durchführt, wenn die Klasse Wochentage mehrfach ins System geladen wurde.

Identitäten von Objekten

Eine Frage, die bei Wertobjekten und Aufzählungen relativ einfach zu beantworten war, ist die nach der Identität von Objekten. Die Frage »Sind zwei Objekte denn identisch?« ist aber für Objekte nicht immer völlig klar zu beantworten. Im folgenden Abschnitt stellen wir vor, wie die Identität von Objekten definiert wird, und zeigen Beispiele, in denen die Frage nach der Identität unterschiedlich beantwortet wird.


Rheinwerk Computing - Zum Seitenanfang

4.4.4 Identität von Objekten  topZur vorigen Überschrift

In der realen Welt ist die Frage nach der Identität in der Regel etwas, was wir sehr einfach beantworten können. Wenn wir einen Kollegen abends in der Kneipe treffen, [Oder in Sportverein, Kirche, Moschee oder im Karnevalsumzug, es soll ja nur ein Beispiel sein. ] fragen wir uns nicht: »Hm, ist der nun derselbe oder nur der gleiche Kollege?« Nein, in der Regel ist klar: Das ist genau der, den wir auch von der Arbeit kennen.

Probleme treten hier meist erst auf, wenn wir mit den Namen von Objekten oder Personen hantieren.

Ist der Herr Qwert Zuiop [Dieses Beispiel ist ganz und gar fiktiv. Ähnlichkeiten mit lebenden Personen sind rein zufällig und bedauerlich. ] , über den ich gerade in der Zeitung lese, dass er Tausende von Anlegern um ihr Geld betrogen hat, vielleicht mein Anlageberater, der auch Qwert Zuiop heißt? Ich weiß es nicht ganz genau, obwohl sich ein mulmiges Gefühl und ein gewisser Verdacht breit machen.

In unserem Beispiel entsteht die Frage nach der Identität dadurch, dass wir den gleichen Namen in zwei verschiedenen Kontexten beobachten. Die Frage ist hier: Beziehen sich die beiden Namen auf dieselbe Person?

Mehrere Referenzen auf Objekte

Im Bereich der Objekte wäre die Entsprechung dazu die Fragestellung, ob sich zwei Referenzen auf dasselbe Objekt beziehen. Diese Fragestellung entspricht der Fragestellung, ob die jeweiligen Referenzen gleich sind. In Java werden Objekte grundsätzlich als Referenzen behandelt, deshalb können wir hier über den Operator == feststellen, ob zwei Variablen auf dasselbe Objekt verweisen.

Berater qwert_zuiop_berater = new Berater(1); 
Berater qwert_zuiop_zeitung = qwert_zuiop_berater; 
if (qwert_zuiop_berater == qwert_zuiop_zeitung) { 
    System.out.println("sorry, du bist pleite"); 
}

Listing 4.18    Zwei identische Berater

Zwei identische Objekte

Aber in objektorientierten Systemen können auch Objekte, die wir über diese Prüfung nicht als identisch erkennen, unter einem bestimmten Aspekt identisch sein. Jetzt stellt sich natürlich die Frage: Wie können denn in einem System überhaupt mehrere Objekte angelegt werden, die wir als identisch betrachten, die also auf unsere Nachfrage antworten würden: Ja, wir beide, ich und mein Kumpel, wir sind eigentlich das identische Objekt. Hört sich ja ein bisschen nach einem Anfall von Schizophrenie an.

Schauen wir uns dazu aber einfach unser Beispiel in etwas modifizierter Form an. Dazu passen wir zunächst unsere Beraterklasse an. Wir gehen davon aus, dass es für alle Berater eine übergreifend eindeutige Kennung gibt, so eine Art laufende Nummer im internationalen Beratungsgeschäft, die auch in einer zentralen Beraterdatenbank gepflegt wird. Die Datenbank sorgt dafür, dass diese Kennungen nicht doppelt vergeben werden. In Abbildung 4.39 ist das Vorgehen dargestellt.

Abbildung 4.39    Eindeutige Datenbankkennungen für Berater

Dabei verweisen nun die Variablen qwert_zuiop_berater und qwert_ zuiop_zeitung auf dasselbe Objekt im Speicher. Die beiden verweisen damit auf das identische Objekt. Die Variable meinBerater verweist auf ein anderes Objekt. Da dieses aber dieselbe Datenbankkennung aufweist, wird es ebenfalls als identisch mit dem anderen Objekt betrachtet.

Eine Umsetzung und Anwendung dieser Prüfung ist in Listing 4.19 aufgeführt.

class Berater { 
    int ID; 
    Berater(int ID) {    
        this.ID = ID; 
    } 
    boolean istIdentisch(Berater andererBerater) {  
        return this.ID == andererBerater.ID; 
    } 
}

    // ...

    Berater qwert_zuiop_berater = new Berater(102);   
    Berater qwert_zuiop_zeitung = qwert_zuiop_berater; 
    Berater meinBerater = new Berater(102);  
    if (meinBerater.istIdentisch(qwert_zuiop)) {  
        System.out.println("sorry, immer noch pleite"); 
    }

Listing 4.19    Anwendung einer eindeutigen Kennung für Prüfung der Identität

Die bei der Konstruktion eines Beraterobjekts in Zeile übergebene Kennung wird bei der Identitätsprüfung in Zeile verwendet. Den in den Zeilen und konstruierten Objekten ist dieselbe Kennung zugeordnet. Dadurch stellt sich bei der Prüfung in Zeile heraus, dass die beiden identisch sind und das angesparte Geld wahrscheinlich futsch ist.

Möglich wird diese Prüfung erst dadurch, dass wir unseren Objekten in diesem Fall einen eindeutigen Schlüssel zuordnen können. Damit schaffen wir eine modifizierte Art von Objektidentität: die Identität mit Bezug auf einen Schlüssel. Da dieser Schlüssel meist auf einen Schlüssel in einer Datenbank abbildet, sprechen wir in diesem Fall von Datenbankidentität.

Datenbankidentität


Datenbankidentität

Bei Objekten, die in Datenbanken gespeichert werden, kann eine eindeutige Kennung für diese Objekte über den zum Objekt gehörenden Eintrag in der Datenbank verwaltet werden. Objekten wird beim Erstellen und Speichern von der Datenbank eine eindeutige Kennung zugeordnet. Beim Laden von Objekten aus der Datenbank wird diese Kennung ebenfalls wieder dem Objekt zugeordnet.

Datenbankidentität wird eine Umsetzung der Prüfung auf Identität genannt, bei der die von der Datenbank vergebene Kennung als Identitätskriterium verwendet wird.


In Kapitel 6, »Persistenz«, werden Sie im Detail erfahren, welche Rolle diese über die Datenbank vergebenen Kennungen im Rahmen der Persistenz von Objekten spielen.

Betrachtungsebenen

Hier haben Sie also einen modifizierten Begriff von Identität: Sie haben zwei unterschiedliche Objekte vorliegen, die aber auf einer anderen Betrachtungsebene auf genau ein Objekt abbilden. Im Fall des Beraters Zuiop haben Sie also durchaus zwei Objekte mit zunächst unterschiedlicher Objektidentität vorliegen. Wenn Sie diese allerdings auf der Ebene der gespeicherten Daten betrachten, beziehen sich beide wieder auf dasselbe Objekt, in diesem Fall denselben Datensatz. Unter diesem Aspekt sind beide Objekte identisch.

So lässt sich auch erklären, wie überhaupt zwei identische Objekte in Ihr System kommen können. Sie könnten zum Beispiel Herrn Qwert Zuiop über zwei Abfragen in der Datenbank geladen haben. Die erste Abfrage liefert einfach alle Berater aus dem Gebiet von Hamburg. Die zweite Abfrage liefert alle betrügerischen Berater. Und da Herr Zuiop in beiden Listen auftaucht, kann es uns passieren, dass wir auf einmal zwei Objekte im System haben, die sich auf denselben Datensatz in der Datenbank beziehen. Die beiden Zuiops sind unter dem Aspekt der Datenbank identisch.

Frage nach der Identität

Nicht immer ist die Frage nach der Identität also einfach zu beantworten. Wir unterscheiden eine Reihe von Situationen, bei denen sich die Frage nach Objektidentität jeweils unterschiedlich beantworten lässt.

  • Wertobjekte haben keine relevante Identität. Die Zahl 17 ist immer die Zahl 17, unabhängig davon, in welchem Kontext sie auftaucht.
  • Identische Objekte können technisch unterschieden sein. Ein Beispiel dafür sind zweifach geladene Objekte mit gleicher Datenbankidentität.
  • Es gibt fachliche Objekte, die auf einer Betrachtungsebene eine Identität haben, auf einer anderen Betrachtungsebene aber auf mehrere Objekte mit jeweils eigener Identität abgebildet werden. Zum Beispiel handelt es sich bei einem Babyfoto und einem Foto eines erwachsenen Menschen auf der Ebene der Fotos um zwei verschiedene Objekte, auch wenn der fotografierte Mensch derselbe ist. So kann man auch zum Beispiel zwei verschiedene Versionen desselben Objekts auf einer anderen Ebene als zwei eigenständige Objekte betrachten.
  • Es gibt Abbildungen aufgrund von technischen Abstraktionen. Stateless Session Beans können technisch in beliebig vielen Exemplaren vorliegen, die verwendete Fassade stellt diese jedoch nach außen als ein und dasselbe Objekt dar.

Um den letzten Fall zu erläutern, werfen wir zunächst einen Blick auf die sogenannten Enterprise Java Beans.

Enterprise Java Beans


Enterprise Java Beans

Enterprise Java Beans (EJB) sind in Java programmierte Klassen, die bestimmte Dienste implementieren. Diese Klassen laufen auf einem Server, in einem EJB-Container. Je nach dem, wie der Zustand der Beans verwaltet wird, unterscheidet man folgende Arten der Enterprise Java Beans:

Die Session Beans sind Objekte, die keinen fachlich gespeicherten Zustand haben. Sie existieren nur für die Dauer der Konversation (Session) zwischen einem Client und dem Server. Dabei unterscheidet man zwischen Stateful und Stateless Session Beans. Die Stateful Session Beans merken sich den Zustand (State) der Konversation zwischen den Aufrufen, die Stateless Session Beans merken sich den Zustand der Session zwischen den Aufrufen nicht.

Im Gegensatz zu den Stateful Session Beans, die an eine Session gebunden sind, kann der Server ein Exemplar der Stateless Session Beans nacheinander in mehreren Sessions verwenden, und er kann auch innerhalb einer Session nacheinander mehrere Exemplare verwenden.

Entity Beans sind Objekte, deren Lebensdauer über eine Session hinausgeht. Dies unterscheidet sie von Session Beans, denn der Zustand der Entity Beans muss auch zwischen den Sessions gespeichert werden. Über Entity Beans lassen sich somit persistente Dienste abbilden. Über einen Primärschlüssel werden diese Beans eindeutig identifiziert, so dass sie persistent gespeichert und anschließend wieder geladen werden können.

Der Vollständigkeit halber erwähnen wir hier noch die sogenannten Message Driven Beans. Diese werden verwendet, um Nachrichten asynchron zu verarbeiten.


Icon Beispiel Verschiedene Beans

Am Beispiel der verschiedenen Arten von Beans lassen sich die unterschiedlichen Sichten auf die Identität von Objekten gut erläutern. Jedes EJB-Objekt weist eine Methode IsIdentical auf. Ein EJB-Objekt ist abhängig von der Art seiner Erzeugung ein Exemplar einer der beschriebenen Arten von Enterprise Beans. Abhängig davon, um welche Art von Enterprise Bean es sich handelt, verhält sich die Abfrage auf Identität unterschiedlich.

  • Exemplare von Stateless Session Beans, die über die gleiche Fabrik (EJB-Home) erzeugt worden sind, sind aus Sicht eines nutzenden Clients alle identisch.
  • Exemplare von Stateful Session Beans, die über die gleiche Fabrik erzeugt worden sind, sind nur dann identisch, wenn es sich tatsächlich um dasselbe Objekt handelt.
  • Exemplare von Entity Beans (persistente Objekte) sind dann identisch, wenn sie den gleichen Wert für ihren Primärschlüssel aufweisen.

Stateless Session Beans

Alle Exemplare einer Stateless Session Bean werden als identisch betrachtet. Da es keinen Zustand gibt, sind diese Exemplare völlig ununterscheidbar und gelten damit alle als identisch:

MyStatelessBean beanA = MyStatelessBeanHome.create(); 
MyStatelessBean beanB = MyStatelessBeanHome.create(); 
assert(beanA.IsIdentical(beanB));

Aus der Sicht des Servers, des Containers der Beans, handelt es sich bei den Exemplaren der Stateless Session Beans möglicherweise um unterschiedliche Objekte, die unterschiedliche Identitäten, Lebenszyklen und Daten haben, aus der Sicht des Clients handelt es sich aber um dasselbe Objekt, da er sie in keiner Weise aufgrund ihres Verhalten unterscheiden kann. Faktisch weiß er gar nicht, dass hier möglicherweise mehrere Exemplare vorhanden sind.

Diese Situation ähnelt den Anrufen bei der Auskunft. Als Anrufer braucht man nicht zu unterscheiden, mit wem man spricht, denn der konkrete Ansprechpartner ist für die Dienstleistung irrelevant. Als Betreiber eines Callcenters muss man aber die einzelnen Mitarbeiter selbstverständlich als Individuen behandeln. Diese Analogie ist in Abbildung 4.40 dargestellt.

Abbildung 4.40    Einheitliche Sicht auf ein Callcenter

Stateful Session Beans

Anders sieht es bei Stateful Session Beans aus. Diese haben eine eigene Identität, die aber nicht über einen expliziten Schlüssel bekannt ist. Die Identität ist wichtig, ihre Verwaltung ist aber eine interne Angelegenheit des Containers, der die Beans verwaltet.

MyStatefulBean beanA = MyStatefulBeanHome.create(); 
MyStatefulBean beanB = MyStatefulBeanHome.create(); 
MyStatefulBean beanC = beanA; 
assert(beanA.IsIdentical(beanC)); 
assert(!(beanA.IsIdentical(beanB)));

In diesem Fall sind nur solche Beans identisch, die dasselbe Objekt referenzieren. Dies entspricht der Situation, in der es lediglich mehrere Referenzen auf dasselbe Objekt geben kann. Die Objekte haben dabei aber keine weiter eingegrenzte Identität, die über ihr Objektsein hinausgeht.

Entity Beans

Schließlich haben wir noch die persistente Variante, die Entity Beans. Diese benötigen, damit sie gespeichert werden können, einen eindeutigen Schlüssel, der mit der Methode getPrimaryKey() erfragt werden kann. Es wäre in diesem Fall zwar ein Fehler, wenn mehrere Objekte mit dem gleichen Primärschlüssel existieren würden. Allerdings könnte ein Container auch hier wieder aus Effizienzgründen mehrere Objekte mit dem gleichen Primärschlüssel verwalten. Er ist dann aber dafür verantwortlich, diese Objekte nach außen wie ein einziges wirken zu lassen.

MyEntityBean beanA = MyEntityBeanHome.create("key1"); 
MyEntityBean beanB = 
           MyEntityBeanHome.findByPrimaryKey("key1"); 
assert(beanA.IsIdentical(beanB));

Auf der Seite des Servers haben wir damit allerdings nichts darüber ausgesagt, ob es sich hier wirklich um das identische Objekt handelt. Beziehen sich nun beanA und beanB auf genau dasselbe Objekt, das vom Container verwaltet wird? Wir wissen es nicht, und es interessiert uns an dieser Stelle auch nicht. beanA und beanB sind für uns identisch, der Primärschlüssel identifiziert unser Objekt eindeutig. Der Rest ist Sache des Containers, der uns die gewünschte Abstraktionsebene bereitstellt.



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