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 5 Vererbung und Polymorphie
  Pfeil 5.1 Die Vererbung der Spezifikation
    Pfeil 5.1.1 Hierarchien von Klassen und Unterklassen
    Pfeil 5.1.2 Unterklassen erben die Spezifikation von Oberklassen
    Pfeil 5.1.3 Das Prinzip der Ersetzbarkeit
    Pfeil 5.1.4 Abstrakte Klassen, konkrete Klassen und Schnittstellen-Klassen
    Pfeil 5.1.5 Vererbung der Spezifikation und das Typsystem
    Pfeil 5.1.6 Sichtbarkeit im Rahmen der Vererbung
  Pfeil 5.2 Polymorphie und ihre Anwendungen
    Pfeil 5.2.1 Dynamische Polymorphie am Beispiel
    Pfeil 5.2.2 Methoden als Implementierung von Operationen
    Pfeil 5.2.3 Anonyme Klassen
    Pfeil 5.2.4 Single und Multiple Dispatch
    Pfeil 5.2.5 Die Tabelle für virtuelle Methoden
  Pfeil 5.3 Die Vererbung der Implementierung
    Pfeil 5.3.1 Überschreiben von Methoden
    Pfeil 5.3.2 Das Problem der instabilen Basisklassen
    Pfeil 5.3.3 Problem der Gleichheitsprüfung bei geerbter Implementierung
  Pfeil 5.4 Mehrfachvererbung
    Pfeil 5.4.1 Mehrfachvererbung: Möglichkeiten und Probleme
    Pfeil 5.4.2 Delegation statt Mehrfachvererbung
    Pfeil 5.4.3 Mixin-Module statt Mehrfachvererbung
    Pfeil 5.4.4 Die Problemstellungen der Mehrfachvererbung
  Pfeil 5.5 Statische und dynamische Klassifizierung
    Pfeil 5.5.1 Dynamische Änderung der Klassenzugehörigkeit
    Pfeil 5.5.2 Entwurfsmuster »Strategie« statt dynamischer Klassifizierung


Rheinwerk Computing - Zum Seitenanfang

5.5 Statische und dynamische Klassifizierung  Zur nächsten ÜberschriftZur vorigen Überschrift

Bisher gingen wir davon aus, dass ein Objekt seine Klassenzugehörigkeit während seiner gesamten Existenz nicht ändert. Die Beziehung zwischen einem Objekt und der dazugehörigen Klasse war nicht änderbar – die Klassifizierung des Objekts war statisch.

Änderung der Klassenzugehörigkeit

Allerdings: Manchmal haben wir in der Praxis Szenarien, in denen sich die Klassenzugehörigkeit eines Objekts während seiner Existenz ändert. So kann aus einem Interessenten ein Kunde oder aus einem externen Berater ein interner Mitarbeiter werden. In solchen Fällen sprechen wir von einer dynamischen Klassifizierung.

Die dynamische Klassifizierung spielt meistens eine Rolle in den konzeptionellen Modellen, in der Programmierung kann man sie seltener sehen. Ein Grund dafür wird wohl die Tatsache sein, dass die klassenbasierten Programmiersprachen wie Java, C#, C++, Python oder Ruby sie nicht unterstützen. [In C++ wird die Klassenzugehörigkeit eines polymorphen Objekts durch den Pointer auf die Tabelle seiner virtuellen Methoden realisiert. Mit direkter Speichermanipulation ist es daher doch möglich, die Klassenzugehörigkeit eines Objekts in C++ dynamisch zu ändern. Es ist jedoch ein gewagtes Spiel mit dem Feuer, so etwas zu machen. Spiele mit dem Feuer sind aufregend und interessant, und wer sie mag, sollte über die Karriere eine Stuntmans oder eines Zirkusartisten statt eines Softwareentwicklers nachdenken. ]


Rheinwerk Computing - Zum Seitenanfang

5.5.1 Dynamische Änderung der Klassenzugehörigkeit  Zur nächsten ÜberschriftZur vorigen Überschrift

Es gibt allerdings auch Sprachen, die eine Änderung der Klassenzugehörigkeit explizit unterstützen. Eine dieser Sprachen ist das Common Lisp Object System, CLOS. Wir werden deshalb zunächst einmal am Beispiel von CLOS vorstellen, wie wir eine solche Anpassung der Klassenzugehörigkeit vornehmen könnten, wenn die Sprache das unterstützt. Im Anschluss daran werden wir dann zeigen, wie wir einen entsprechenden Mechanismus durch die Verwendung von Strategien auch in den etwas gängigeren Sprachen wie Java umsetzen können.

In einer Sprache wie CLOS sind Mechanismen vorgesehen, die es erlauben, den Typ eines Objekts auf definierte Weise zur Laufzeit eines Programms zu verändern.

Wir machen aus einem A ein B.

Wenn wir Objekt x (ein Exemplar der Klasse A) zu einem Exemplar der Klasse B machen, werden dabei alle Attribute übernommen, die in A und in B vorkommen. Zusätzlich wird (sofern vorhanden) eine Methode update-instance-for-different-class aufgerufen, in der neu zu initialisierende Datenelemente, die in B definiert sind, aber von A nicht bereitgestellt werden, mit Werten belegt werden können. Der Rest ist einfach, da x nun ein Exemplar von B ist, werden auch die entsprechenden Aufrufe von Operationen den Methoden von B zugeordnet.

Geschäftspartner: Gäste, Interessenten und Kunden

Nehmen wir als Beispiel eine Anwendung, in der Geschäftspartner als Gäste, Interessenten oder Kunden klassifiziert werden. Dabei können diese vom Gast zum Interessenten und schließlich zum Kunden werden. Die entsprechende Hierarchie ist in Abbildung 5.70 dargestellt.

Abbildung 5.70    Hierarchie von Geschäftspartnern

Die Definition der Klassen sieht in CLOS dann wie unten stehend aus.

;; Definition der beteiligten Klassen 
(defclass geschaeftspartner() 
    ((name :initarg :name :accessor name) 
     (vorname :initarg :vorname :accessor vorname) 
    ) 
) 
(defclass interessent (geschaeftspartner) 
    ((erwarteter-umsatz :initarg :umsatz :accessor umsatz)) 
) 
 
(defclass gast (geschaeftspartner) 
    ((zeitbudget :initarg :zeitbudget :accessor zeitbudget)) 
) 
 
(defclass kunde (geschaeftspartner) 
    ((einstufung :initarg :einstufung :accessor einstufung)) 
)

Listing 5.33    Festlegung einer Klassenhierarchie in CLOS

Alle beteiligten Klassen haben unterschiedliche Implementierungen der Operation display. Da in den spezifischen Klassen auch weitere Attribute hinzukommen, ist die Darstellung eines Geschäftspartners in allen abgeleiteten Klassen unterschiedlich.

;; Display-Methoden für die spezifischen Klassen 
(defmethod display ((gp geschaeftspartner)) 
    (princ (string-concat "Geschäftspartner " (firstname gp) " "  (name gp))) 
) 
(defmethod display((gp interessent)) 
    (call-next-method) ;; ruft die Methode der Basisklasse 
    (princ (string-concat " ist ein Interessent mit erwartetem Umsatz " (umsatz gp))) 
) 
 
(defmethod display((gp gast)) 
    (call-next-method) ;; ruft die Methode der Basisklasse 
    (princ (string-concat " ist ein Gast mit Zeitbudget " (zeitbudget gp))) 
) 
 
(defmethod display((gp kunde)) 
    (call-next-method) ;; ruft die Methode der Basisklasse 
    (princ (string-concat " ist ein Kunde mit Einstufung " (einstufung gp))) 
)

Listing 5.34    Unterschiedliche Darstellungen für Geschäftspartner

Gast wird zu einem Interessenten.

Nun wollen wir für Exemplare der jeweiligen Klassen deren Beschreibung ausgeben lassen. Dabei lassen wir einfach nebenbei einen Gast seine Klasse auf interessent wechseln. Dies geschieht in CLOS über die Methode change-class.

;; ... 
        (frieda (make-instance 'gast :vorname "Frieda" 
                 :name "Müller" :zeitbudget "100 Minuten")) 
        (gerd (make-instance 'interessent :vorname "Gerd" 
                 :name "Müller" :umsatz "200 Euro")) 
        (anne (make-instance 'kunde :vorname "Anne" 
                    :name "Müller" :einstufung "mittel")) 
        ) 
        ;; ... 
        (display frieda) 
        (display gerd) 
        (display anne) 
        (change-class frieda 'interessent) 
        (display frieda) 
        ;; ...

Wir erhalten die folgende Ausgabe vom Interpreter:

Geschäftspartner Frieda Müller ist ein Gast  
mit Zeitbudget 100 Minuten. 
 
Geschäftspartner Gerd Müller ist ein Interessent  
mit erwartetem Umsatz 200 Euro. 
Geschäftspartner Anne Müller ist ein Kunde  
mit Einstufung mittel. 
 
Geschäftspartner Frieda Müller 
*** – SLOT-VALUE: The slot ERWARTETER-UMSATZ of #<INTERESSENT 
#x19F192C1> has no value

Vorbelegen von neuen Attributen

Ups, wir haben Frieda Müller vom Gast zur Interessentin befördert, ohne ihr aber einen Wert für das benötigte Attribut erwarteter-umsatz zu geben. Das heißt, beim Ändern einer Klasse sind spezielle neue Initialisierungen für ein Objekt notwendig. CLOS sieht dafür die generische Funktion update-instance-for-different-class vor.

(defmethod update-instance-for-different-class ((gast gast) 
(interessent interessent) &rest initargs) 
    (setf (umsatz interessent) "100 Euro") 
)

Wenn wir die Methode wie oben aufgeführt überschreiben, wird diese beim Wechsel eines Objekts von der Klasse gast zur Klasse interessent aufgerufen. Unsere Ausgabe sieht danach wie unten stehend aus:

Geschäftspartner Frieda Müller ist ein Gast  
mit Zeitbudget 100 Minuten. 
Geschäftspartner Gerd Müller ist ein Interessent  
mit erwartetem Umsatz 200 Euro. 
Geschäftspartner Anne Müller ist ein Kunde  
mit Einstufung mittel. 
Geschäftspartner Frieda Müller ist ein Interessent  
mit erwartetem Umsatz 100 Euro.

Wir können das tun. Sollen wir es auch?

Obwohl die Programmiersprache hier einfache und intuitive Mechanismen bereitstellt, um ein Objekt die Klasse wechseln zu lassen, kann ein derartiges Vorgehen die Komplexität von Programmen erhöhen. Auch wenn eine Programmiersprache ein solches Vorgehen zulässt: Besser ist meistens die Anwendung des Entwurfmusters Strategie. Dieses lässt sich auch in Sprachen anwenden, die keine dynamische Klassifizierung unterstützen.

Es besteht nämlich auch ein grundsätzliches konzeptionelles Problem bei der Änderung der Klassenzuordnung zur Laufzeit. Wie bei jeder anderen Änderung eines Objekts müssen wir nämlich auch bei der Änderung seiner Klassenzugehörigkeit dafür sorgen, dass das Objekt alle Versprechen, die es gegeben hat, weiterhin erfüllt. Das Prinzip der Ersetzbarkeit verlangt genau das.

Was bedeutet das? In den statisch typisierten Programmiersprachen bestimmt die Klassenzugehörigkeit den Typ des Objekts. Die Klassenzugehörigkeit bestimmt also, ob eine Variable das Objekt enthalten beziehungsweise referenzieren kann. Ändert sich die Klassenzugehörigkeit des Objekts, müssen wir dafür sorgen, dass nur solche Variablen das Objekt referenzieren, die auch mit seinem neuen Typ kompatibel sind.

Subtypmigration

Es gibt aber eine Möglichkeit, dies zu gewährleisten und trotzdem die gewünschte Änderung des Verhaltens zu erreichen. Sie müssen dafür sorgen, dass die Variablen als ihren Typ nur eine Oberklasse aller möglichen Klassen, zu denen das Objekt im Laufe seiner Existenz mutieren kann, haben können.

Die echte Klassenzugehörigkeit des Objekts darf also nicht sichtbar sein, nur seine Klassenzugehörigkeit zu einer Oberklasse darf bekannt sein. Und diese ändert sich nicht. Es ändert sich nur der von außen unsichtbare Subtyp des Objekts. Das Objekt ändert nicht seine Schnittstelle, es ändert nur die Implementierung.

Im folgenden Abschnitt werden wir sehen, wie wir genau dieses Verhalten durch die Anwendung des Entwurfsmusters »Strategie« erreichen.


Rheinwerk Computing - Zum Seitenanfang

5.5.2 Entwurfsmuster »Strategie« statt dynamischer Klassifizierung  topZur vorigen Überschrift

Wenn sich die nach außen sichtbare Klassenzugehörigkeit eines Objekts nicht ändert, kann man die Änderung der eigentlichen Klassenzugehörigkeit auch in einer Programmiersprache umsetzen, die keine dynamische Klassifizierung unterstützt.

Das Mittel der Wahl ist in diesem Fall das Entwurfsmuster »Strategie«.


Icon Hinweis Entwurfsmuster »Strategie«

Wenn sich ein Teil des Verhaltens der Exemplare einer Klasse abhängig von ihrem Zustand verändern kann, kann man die verschiedenen Verhaltensweisen in separate Strategieklassen auslagern.

Jedes Exemplar der Hauptklasse besitzt zu jedem Zeitpunkt ein Exemplar einer der Strategieklassen, auf das es die Implementierung seines Verhaltens delegiert. Ändert sich der Zustand des Objekts so, dass eine Veränderung der Verhaltens nötig wird, tauscht das Objekt sein Strategieobjekt aus. Durch die Anwendung dieses Musters entkoppelt man die verschiedenen Verhaltensvarianten der Exemplare der Hauptklasse, ohne auf die dynamische Klassifizierung zurückgreifen zu müssen.


Greifen wir eine etwas modifizierte Variante unseres Beispiels aus Abbildung 5.70 wieder auf, und nehmen wir an, wir schreiben eine E-Commerce-Internetanwendung.

Die Besucher unserer Seite können sich registrieren und Ware bestellen. Der Inhalt der Seite wird für jeden Benutzer speziell aufbereitet. Benutzer, die noch nichts bestellt haben, bekommen andere Werbung und Aktionen präsentiert als die Bestandskunden. Die Neukunden können nur per Vorkasse bezahlen, den Premiumkunden wird eine Ratenzahlung angeboten. Die Zugehörigkeit jedes Benutzers zu diesen Kategorien kann sich mit der Zeit ändern.

Eine mögliche Realisierung ist eine Klasse User, die den aktuellen Status des Benutzers kennt und ihn in ihren Methoden auswertet. Listing 5.35 zeigt eine Java-Implementierung einer solchen Klasse.

public class User { 
  private enum Status { 
    PROSPECT, NEW_CUSTOMER, ORDINARY_CUSTOMER, VIP_CUSTOMER 
  } 
 
  private Status status; 
 
  public void displayAds() { 
    switch (status) { 
    case PROSPECT: 
      // Werbung für Interessenten 
      break; 
 
    case NEW_CUSTOMER: 
      //  Werbung für Neukunden 
      break; 
 
    ... // und so weiter 
  } 
  ... 
}

Listing 5.35    Problematische Lösung: Auswertung von Kundenstatus in Klasse »User«

Wir müssen davon ausgehen, dass solche switch-Befehle nicht nur in der Methode displayAds, sondern in sehr vielen anderen Methoden der Klasse User vorkommen. Das ist nicht besonders übersichtlich und führt zu großem Aufwand, wenn sich die Kategorisierung der Besucher ändert.

So könnten wir in der Zukunft bestimmte Kunden, die gern bestellen, aber ungern zahlen, einer neuen Kategorie zuordnen, die ein neues Verhalten der Internetseite bewirken sollte. Dies würde bedeuten, dass wir viele Methoden und viele switch-Befehle sichten und anpassen müssen.

In Abbildung 5.71 sehen Sie eine Möglichkeit, dieses Problem durch die Anwendung des Entwurfsmusters »Strategie« zu lösen.

Abbildung 5.71    Entwurfsmuster »Strategie« angewendet

Strategieklassen

Unsere Anwendung wird übersichtlicher, wenn wir das Verhalten der unterschiedlichen Benutzerkategorien in unterschiedlichen Klassen unterbringen – in Strategieklassen wie zum Beispiel ProspectStrategy und NewCustomerStrategy. In Abschnitt 5.4.2 haben wir das Konzept der Delegation beschrieben. Exemplare von Strategieklassen sind Objekte, an die der Aufruf von Operationen delegiert wird. Zusätzlich können wir diese Objekte zu definierten Zeitpunkten auswechseln, um darüber das Verhalten eines Objekts zu ändern.

Wenn Sie das Muster »Strategie« auf unser Beispiel anwenden, besitzt jedes Exemplar der Klasse User zu jedem Zeitpunkt genau ein Exemplar einer der Unterklassen von UserStrategy und delegiert die Aufrufe der statusspezifischen Operationen an das Strategieobjekt.

Ändert sich der Status des Benutzers, wird zum Beispiel aus einem Interessenten ein Neukunde, bekommt das User-Objekt ein neues Strategieobjekt. So ändert sich sein Verhalten, nach außen ändert sich der Typ des User-Objekts jedoch nicht.

Beispiel für Strategieklassen

In Listing 5.36 ist die Umsetzung dieses Konzepts für unser Beispiel in Java dargestellt.

public class Guest { 
 
  private abstract class Strategy { 
    // gibt die Werbung aus 
    public abstract void displayAds(); 
    // bearbeitet eine Bestellung und gibt das neue 
    // Strategieobjekt des Besuchers zurück 
    public abstract Strategy makePurchase(...); 
    ... andere Methoden der Strategieobjekte 
  } 
 
  // das aktuelle Strategieobjekt des Besuchers 
  private Strategy strategy; 
 
  private void displayAds()  { 
    // delegiere den Aufruf an das Strategieobjekt 
    strategy.displayAds(); 
  } 
 
  public void makePurchase() { 
    // delegiere den Aufruf und merke dir das neue 
    // Strategieobjekt 
    strategy = strategy.makePurchase(); 
  } 
 
  private class ProspectStrategy extends Strategy { 
    @Override 
    public void displayAds() { 
      // Werbung für Interessenten anzeigen 
    } 
 
    @Override 
    public Strategy makePurchase() { 
      // die Erstbestellung bearbeiten ... 
      // Aus einem Interessenten wird jetzt ein Neukunde 
      return new NewCustomerStrategy(this); 
    } 
    ... 
  } 
 
  private class NewCustomerStrategy extends Strategy { 
    public NewCustomerStrategy(ProspectStrategy strategy) { 
      // Übernehme eventuell die Informationen aus 
      // dem vorherigen Status 
    } 
 
    @Override 
    public void displayAds() { 
      // Werbung für Neukunden anzeigen 
    } 
 
    @Override 
    public Strategy makePurchase() { 
      // die weiteren Bestellungen eines Neukunden 
      // bearbeiten. 
      // Dies ändert den Status des Kunden nicht, erst der 
      // Zahlungseingang macht aus einem Neukunden einen 
      // gewöhnlichen Kunden 
      return this; 
    } 
    ... 
  } 
  ... 
}

Listing 5.36    Umsetzung von Strategien für Kunden

Die jeweilige Strategie legt zum einen über die Umsetzung der Operation displayAds fest, welche Werbung der Kunde zu sehen bekommt. Zum anderen legt sie über die Umsetzung der Operation makePurchase auch fest, wie mit einer Bestellung umgegangen wird. Die Operation makePurchase kann auch dazu führen, dass ein Kunde vom Prospect zum Customer befördert wird. Deshalb gibt die Methode makePurchase der Klasse ProspectStrategy ein neues Strategie-Objekt zurück, das dann in Zukunft verwendet wird. ProspectStrategy löst sich damit praktisch selbst ab, sehr uneigennützig.

Das vorgestellte Entwurfsmuster ist besonders geeignet für alle Fälle, in denen Sie zur Laufzeit das Verhalten von Exemplaren einer Klasse ändern wollen.



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