Tải bản đầy đủ
5 Prozedurale Entwürfe in Objekte überführen

5 Prozedurale Entwürfe in Objekte überführen

Tải bản đầy đủ

Sandini Bib
12.5 Prozedurale Entwürfe in Objekte überführen

12.5.1

381

Motivation

Einer unserer Kunden begann einmal ein Projekt mit zwei absoluten Prinzipien,
die die Entwickler zu befolgen hatten: 1. Sie müssen Java verwenden, 2. Sie dürfen
keine Objekte verwenden.
Wir mögen darüber lachen. Aber obwohl Java eine objektorientierte Sprache ist,
gehört doch mehr dazu, Objekte zu verwenden, als einen Konstruktor aufzurufen.
Objekte verwenden zu lernen erfordert eine gewisse Zeit. Oft stehen Sie vor dem
Problem, dass Sie prozeduralen Code haben, der stärker objektorientiert werden
muss. Typisch sind lange prozedurale Methoden auf Klassen mit wenig Daten
und einfache Datenobjekte mit wenig mehr als Zugriffsmethoden. Wenn Sie ein
rein prozedurales Programm konvertieren, so haben Sie vielleicht nicht mal das,
aber es ist ein guter Ausgangspunkt.
Wir sagen nicht, dass Sie nie Objekte mit Verhalten und wenigen oder keinen Daten haben dürfen. Wir verwenden oft kleine Strategieobjekte, wenn wir Verhalten
variieren müssen. Aber solche prozeduralen Objekte sind meistens klein und werden benutzt, wenn wir einen hohen Bedarf an Flexibilität haben.

12.5.2


Vorgehen

Machen Sie aus jeder Satzart eine einfache Datenklasse mit Zugriffsmethoden.

➾ Haben Sie eine relationale Datenbank, so machen Sie aus jeder Tabelle eine einfache Datenklasse.


Nehmen Sie den ganzen prozeduralen Code, und bringen Sie ihn in einer Klasse unter.

➾ Sie können die Klasse entweder als Singleton realisieren (für die einfache Neuinitialisierung) oder alle Methoden als statisch deklarieren.


Nehmen Sie sich jede lange Methode vor und wenden Sie Methode extrahieren
(106) und die verwandten Refaktorisierungen an, um sie zu zerlegen. Während
Sie die Prozeduren zerlegen, verwenden Sie Methode verschieben (139), um jede
Prozedur in die geeignete einfache Datenklasse zu verschieben.



Fahren Sie fort, bis Sie das ganze Verhalten aus der Originalklasse entfernt haben. War die Originalklasse eine rein prozedurale Klasse, so sollten Sie sie entfernen.

Sandini Bib
382

12.5.3

12 Große Refaktorisierungen

Beispiel

Das Beispiel aus Kapitel 1 ist ein gutes Beispiel für die Notwendigkeit, Prozedurale
Entwürfe in Objekte überführen einzusetzen, insbesondere im ersten Schritt, in dem
die Methode statement zerlegt und verteilt wird. Wenn Sie damit fertig sind, können Sie an den nun intelligenten Datenobjekten mit anderen Refaktorisierungen
arbeiten.

12.6

Anwendung von der Präsentation trennen

Sie haben GUI-Klassen, die Anwendungslogik enthalten.
Trennen Sie die Anwendungslogik in separate Klassen ab.

Order Window


Order Window

Order

1

12.6.1

Motivation

Jedes Mal, wenn Fachleute über Objekte reden, hören Sie etwas von Model-ViewController (MVC, Beobachtermuster). Diese Idee steckt hinter dem Verhältnis
von grafischer Benutzerschnittstelle (GUI) und den Anwendungsobjekten in
Smalltalk-80.
Der wertvolle Kern von MVC ist die Trennung von Benutzerschnittstellencode
(der Sicht (view), heute oft als Präsentation bezeichnet) und der Anwendungslogik (dem Modell (model)). Die Präsentationsklassen enthalten nur die Logik, die
notwendig ist, um mit der Benutzerschnittstelle umzugehen. Anwendungsobjekte enthalten keinen visuellen Code, aber alle Geschäftslogik. Dies trennt zwei
komplizierte Programmteile in Stücke, die leicht zu ändern sind. Es ermöglicht

Sandini Bib
12.6 Anwendung von der Präsentation trennen

383

mehrere Präsentationen derselben Geschäftslogik. Wer Erfahrung im Arbeiten
mit Objekten hat, verwendet diese Trennung instinktiv, und sie hat ihren Wert
bewiesen.
Aber so legen die meisten Entwickler, die mit GUIs arbeiten, ihr Design nicht an.
Die meisten Umgebungen mit Client-Server-GUIs verwenden ein Zweischichtendesign: Die Daten liegen in der Datenbank und die Logik in den Präsentationsklassen. Die Umgebung erzwingt oft diesen Stil und macht es Ihnen schwer, die
Logik an anderer Stelle unterzubringen.
Java ist eine richtige objektorientierte Umgebung. Sie können daher nicht visuelle
Anwendungsobjekte erstellen, die Anwendungslogik enthalten. Häufig begegnen
Sie aber Code, der in diesem Zweischichtenstil geschrieben ist.

12.6.2

Vorgehen



Erstellen Sie eine Anwendungsklasse für jedes Fenster.



Haben Sie eine Tabelle im Fenster, so erstellen Sie eine Klasse für jede Zeile in
der Tabelle. Verwenden Sie eine Collection in der Anwendungsklasse des Fensters für die Anwendungsobjekte in den Zeilen.



Untersuchen Sie die Daten im Fenster. Sind sie nur für Aufgaben der Benutzerschnittstelle da, lassen Sie sie in dem Fenster. Wenn sie in der Anwendungslogik verwendet werden, aber nicht im Fenster dargestellt werden, so verwenden
Sie Feld verschieben (144), um sie in die Anwendungsklasse zu verschieben.
Wenn die Daten sowohl in der Benutzerschnittstelle als auch in der Anwendungslogik verwendet werden, so verwenden Sie Beobachtete Werte duplizieren
(190), so dass sie an beiden Stellen vorhanden sind und die Synchronisierung
garantiert ist.



Überprüfen Sie die Präsentationsklasse. Verwenden Sie Methode extrahieren
(106), um die Logik der Präsentation von der Anwendungslogik zu trennen.
Wenn Sie die Anwendungslogik isoliert haben, verwenden Sie Methode verschieben (139) um sie in die Anwendungsklasse zu verschieben.



Wenn Sie damit fertig sind, haben Sie Präsentationsklassen, die die GUI handhaben, und Anwendungsklassen, die alle Anwendungslogik enthalten. Die Anwendungsobjekte werden noch nicht gut faktorisiert sein, aber damit werden
sich weitere Refaktorisierungen beschäftigen.

Sandini Bib
384

12.6.3

12 Große Refaktorisierungen

Beispiel

Wir haben hier ein Programm, das es Benutzern ermöglicht, Aufträge einzugeben
und die Preise zu ermitteln. Die GUI sieht aus wie in Abbildung 12-7.

Abbildung 12-7 Die Benutzerschnittstelle für das Ausgangsprogramm

Die Präsentationsklasse interagiert mit einer relationalen Datenbank, die in Abbildung 12-8 dargestellt ist.
Das ganze Verhalten, sowohl das der GUI als auch das Ermitteln der Preise für die
Aufträge, befindet sich in einer Klasse OrderWindow.

Sandini Bib
12.6 Anwendung von der Präsentation trennen

385

Customers
All classes are «SQL
Table». Bold attributes
show primary key columns.
«FK» indicates foreign keys

Name: Text
CustomerID: Number
Codes: Text

1
Products


Orders
OrderID: Number
CustomerID: Number «FK»
Amount: Number

OrderLines

1



OrderID: Number «FK»
ProductID: Number «FK»
Quantity: Number
Amount: Number



1

ProductID: Number
Name: Text
Threshold1: Number
Price1: Number
Threshold2: Number
Price2: Number
Threshold3: Number
Price3: Number
Threshold4: Number
Price4: Number

Abbildung 12-8 Die Datenbank für das Auftragsprogramm

Wir beginnen damit, eine geeignete Auftragsklasse Order zu erstellen. Wir verbinden sie mit dem OrderWindow wie in Abbildung 12-9. Da das Fenster eine Tabelle
enthält, um die Auftragszeilen anzuzeigen, erstellen wir auch eine Klasse OrderLine für die Zeilen der Tabelle.

Order Window

Order

1


Order Line

Abbildung 12-9 Auftragsfenster (Order Window) und Auftrag (Order)

Sandini Bib
386

12 Große Refaktorisierungen

Wir gehen vom Fenster aus, nicht von der Datenbank. Ein erstes Modell des Anwendungsbereichs auf einer Datenbank aufzubauen ist eine sinnvolle Strategie.
Unser größtes Risiko ist hier aber die Vermischung von Präsentations- und Anwendungslogik. Wir trennen diese auf Basis des Fensters und refaktorisieren den
Rest später.
Bei dieser Art von Programmen ist es nützlich in den GUI-Klassen, nach eingebetteten SQL-Befehlen (Structured Query Language) zu suchen. Daten aus einem
SQL-Befehl sind Anwendungsdaten.
Das einfachste Anwendungsmodell, mit dem wir arbeiten können, ist nicht direkt
in der GUI zu erkennen. In diesem Fall enthält die Datenbank ein Feld Codes in
der Tabelle Customer. Dieses Feld wird nicht direkt in dem Fenster angezeigt; es
wird in eine für Menschen besser lesbare Form gebracht. Wir können dieses Feld
gefahrlos mittels Feld verschieben (144) in die Anwendungsklasse verschieben.
Mit den anderen Feldern haben wir nicht so viel Glück. Sie enthalten AWT-Komponenten, die in dem Fenster angezeigt und in den Anwendungsobjekten verwendet werden. Für diese müssen wir Beobachtete Werte duplizieren (190) einsetzen. Dies fügt ein Anwendungsfeld in die Klasse Order ein, zu dem es ein
entsprechendes AWT-Feld im OrderWindow gibt.
Dies ist ein langsamer Prozess, aber am Ende haben wir alle Felder für Anwendungslogik in der Anwendungsklasse. Ein guter Leitfaden für diesen Prozess besteht darin zu versuchen, alle SQL-Befehle in die Anwendungsklasse zu verschieben. Sie können die Datenbanklogik und die Anwendungsdaten gemeinsam in
die Anwendungsklasse verschieben. Ob Sie damit fertig sind, können Sie gut feststellen, indem Sie java.sql nicht mehr im OrderWindow importieren. Dies heißt für
Sie, sehr oft Methode extrahieren (106) und Methode verschieben (139) anzuwenden.
Die so entstandenen Klassen in Abbildung 12-10 sind noch weit davon entfernt,
gut faktorisiert zu sein. Aber das Modell reicht aus, um die Anwendungslogik abzutrennen. Bei dieser Refaktorisierung müssen Sie sehr genau darauf achten, wo
Ihre Risiken liegen. Wenn die miteinander verschlungene Präsentations- und Anwendungslogik Ihr größtes Risiko ist, trennen Sie sie vollständig, bevor Sie andere
Dinge angehen. Sind andere Dinge wichtiger, wie die Preisfindungsstrategien für
die Produkte, so ziehen Sie den wichtigsten Teil dieser Logik aus dem Fenster heraus und refaktorisieren darum herum eine geeignete Struktur für das Gebiet mit
dem höchsten Risiko. Wahrscheinlich muss der größte Teil der Anwendungslogik
aus der Klasse OrderWindow entfernt werden. Wenn Sie refaktorisieren können,
aber einige Logik im Fenster belassen müssen, so beginnen Sie mit dem Bereich,
in dem Ihr Risiko am höchsten ist.

Sandini Bib
12.7 Hierarchie extrahieren

387

Order
Order Window

1

ID
customerName
amount
customerCodes


Order Line
productName
quantity
price

Abbildung 12-10 Verteilung der Daten für die Anwendungsklassen

12.7

Hierarchie extrahieren

Sie haben eine Klasse, die zu viele Aufgaben zumindest teilweise durch bedingte
Ausdrücke erledigt.
Erstellen Sie eine Hierarchie von Klassen, in der jede Unterklasse einen Spezialfall repräsentiert.

Billing Scheme


Billing Scheme

Business Billing
Scheme

Residential Billing
Scheme

Disability Billing
Scheme

Sandini Bib
388

12 Große Refaktorisierungen

12.7.1

Motivation

Beim evolutionären Entwurf passiert es häufig, dass Sie sich vorstellen, eine Klasse
implementiere eine Idee, und erst später feststellen, dass sie tatsächlich zwei, drei
oder zehn Ideen implementiert. Zunächst erstellen Sie die Klasse. Einige Tage oder
Wochen später sehen Sie, dass diese Klasse auch an anderer Stelle eingesetzt werden kann, wenn Sie nur einige Steuerungsvariablen und Bedingungen einfügen.
Einen Monat später ergibt sich eine weitere solche Gelegenheit. Ein Jahr später
stehen Sie vor einem Scherbenhaufen: Über die ganze Klasse sind Steuerungsvariablen und bedingte Ausdrücke verstreut.
Wenn Ihnen ein schweizer Armeemesser in die Hände fällt, das so groß geworden
ist, dass Sie damit Dosen öffnen, kleine Bäume fällen, einen Laserstrahl auf widerspenstige Präsentationspunkte richten und – so nehme ich an – Dinge schneiden
können, so brauchen Sie eine Strategie, um die verschiedenen Stränge auseinander zu nehmen. Diese Strategie funktioniert nur, wenn die Verzweigungslogik
während des Lebens eines Objekts statisch bleibt. Wenn nicht, kann es sein, dass
Sie Klasse extrahieren (148) anwenden müssen, bevor Sie beginnen können, die
Fälle voneinander zu trennen.
Seien Sie nicht enttäuscht, wenn Sie feststellen, dass Hierarchie extrahieren eine Refaktorisierung ist, die Sie nicht an einem Tag abschließen können. Es kann Tage
oder Wochen dauern, ein Design zu entwirren, das sich verheddert hat. Machen
Sie einige Schritte, die einfach und offensichtlich sind, dann können Sie eine
Pause einlegen. Machen Sie für einige Tage sichtbare produktive Arbeiten. Wenn
Sie wieder etwas dazugelernt haben, machen Sie an dieser Stelle mit einigen weiteren einfachen und offensichtlichen Schritten weiter.

12.7.2

Vorgehen

Wir präsentieren hier zwei Vorgehensweisen. Im ersten Fall wissen Sie noch nicht
sicher, welche Varianten es gibt. In diesem Fall machen Sie jeweils einen Schritt:


Identifizieren Sie eine Variante.

➾ Falls sich die Variante während des Lebens eines Objekts ändern kann, verwenden
Sie Klasse extrahieren (148), um diesen Aspekt in eine andere Klasse zu verschieben.


Erstellen Sie eine Unterklasse für den Spezialfall, und wenden Sie Konstruktor
durch Fabrikmethode ersetzen (313) auf die Originalklasse an. Lassen Sie die Fabrikmethode ein Objekt der Unterklasse erzeugen, wo dies sinnvoll ist.

Sandini Bib
12.7 Hierarchie extrahieren



389

Kopieren Sie Methoden, die Verzweigungslogik enthalten, Schritt für Schritt in
die Unterklasse. Vereinfachen Sie die Methoden dann so weit, wie dies möglich
ist, wenn Sie berücksichtigen, dass es sich jetzt um Objekte der Unterklasse
handelt und nicht mehr um Objekte der Oberklasse.

➾ Wenden Sie Methode extrahieren (106) auf die Oberklasse an, wenn es notwendig
ist, in Methoden die bedingten Zweige von den unbedingten zu trennen.


Fahren Sie fort, Spezialfälle zu isolieren, bis Sie die Oberklasse als abstrakt deklarieren können.



Löschen Sie die Rümpfe von Methoden in der Oberklasse, die in allen Unterklassen überschrieben werden, und deklarieren Sie diese in der Oberklasse als
abstrakt.

Wenn die Varianten von vornherein klar sind, können Sie die folgende Strategie
einsetzen:


Erstellen Sie eine Unterklasse für jede Variante.



Verwenden Sie Konstruktor durch Fabrikmethode ersetzen (313), um für jede Variante ein Objekt der entsprechenden Unterklasse zu erzeugen.

➾ Wenn die Varianten durch einen Typenschlüssel gekennzeichnet sind, so wenden
Sie Typenschlüssel durch Unterklassen ersetzen (227) an. Wenn sich die Varianten im Laufe des Lebens eines Objekts ändern können, verwenden Sie Typenschlüssel durch Zustand/Strategie ersetzen (231).


Nehmen Sie die Methoden mit Verzweigungslogik, und wenden Sie darauf Bedingten Ausdruck durch Polymorphismus ersetzen (259) an. Wenn sich nicht die
ganze Methode ändert, isolieren Sie den variablen Teil mittels Methode extrahieren (106).

12.7.3

Beispiel

Dieses Beispiel ist ein nicht offensichtlicher Anwendungsfall für diese Refaktorisierung. Sie können den Refaktorisierungen Typenschlüssel durch Unterklassen
ersetzen (227), Typenschlüssel durch Zustand/Strategie ersetzen (231) und Bedingten
Ausdruck durch Polymorphismus ersetzen (259) folgen, um zu sehen, wie der offensichtliche Fall funktioniert.
Wir beginnen mit einem Programm, das eine Stromrechnung erstellt. Die Ausgangsobjekte zeigt Abbildung 12-11.

Sandini Bib
390

12 Große Refaktorisierungen

Das Abrechnungsschema enthält unter verschiedenen Umständen viel Verzweigungslogik. Verschiedene Abrechnungssätze werden für Sommer und Winter verwendet, verschiedene Abrechungspläne gelten für Eigenheime, kleine Betriebe
und Kunden, die Sozialhilfe (Hilfe zum Lebensunterhalt) beziehen oder an einer
Behinderung (Disability) leiden. Daraus ergeben sich komplexe Verzweigungen,
die die Klasse Billing Scheme ziemlich komplex machen.

1

Billing Scheme

Customer
createBill(Customer)
...

Abbildung 12-11 Kunde (Customer) und Abrechnungsschema (Billing Scheme)

Unser erster Schritt besteht darin, eine Variante herauszugreifen, die sich durch
die Verzweigungslogik hindurchzieht. Das kann eine Steuerungsvariable in Customer, Billing Scheme oder sonstwo sein.
Wir erstellen eine Unterklasse für diese Variante. Um die Unterklasse verwenden
zu können, müssen wir sicherstellen, dass sie erzeugt und benutzt wird. Also untersuchen wir den Konstruktor von Billing Scheme. Als Erstes wenden wir Konstruktor durch Fabrikmethode ersetzen (313) an. Dann untersuchen wir die Fabrikmethode, um zu sehen, welche Teile der Logik von einer Behinderung abhängen.
Dann erstellen wir eine Klausel, die ein Objekt der Klasse Disability Billing
Scheme liefert, wenn dies erforderlich ist.
Wir untersuchen die verschiedenen Methoden von Billing Scheme und halten
nach denen Ausschau, deren Verzweigungslogik sich bei Vorliegen einer Behinderung ändert. Eine dieser Methoden ist createBill, die wir daher in die Unterklasse
kopieren. (Siehe Abbildung 12-12)
Wir untersuchen nun die Kopie von createBill in der Unterklasse und vereinfachen sie, da wir uns nun im Kontext eines Disability Billing Scheme befinden.
Wenn im Code beispielsweise
if (disabilityScheme()) doSomething;

steht , so können wir dies durch
doSomething;

ersetzen. Wenn die Abrechnungsschemata für Kunden mit Behinderungen und
kleine Unternehmen sich ausschließen, können wir den ganzen Code, der von
letzterem abhängt, eliminieren.

Sandini Bib
12.7 Hierarchie extrahieren

391

1

Billing Scheme

Customer
createBill(Customer)
...

Disability Billing
Scheme
createBill(Customer)

Abbildung 12-12 Hinzufügen einer Klasse für Disability

Während wir dies tun, wollen wir sicherstellen, dass der veränderliche Code von
unveränderlichem getrennt wird. Wir verwenden hierzu Methode extrahieren (106)
und Bedingung zerlegen (242). Wir fahren fort, dies für verschiedene Methoden
von Billing Scheme zu tun, bis wir das Gefühl haben, mit den meisten Bedingungen im Zusammenhang mit Behinderungen etwas Sinnvolles getan zu haben.
Dann nehmen wir uns eine andere Variante vor (Hilfe zum Lebensunterhalt) und
machen für diese das Gleiche.
Während wir uns mit der zweiten Variante beschäftigen, untersuchen wir aber
auch, wie sich die Bedingungen bei Hilfe zum Lebensunterhalt von denen bei Behinderung unterscheiden. Wir wollen die Fälle finden, in denen beide Methoden
das gleiche Ziel haben, es aber auf unterschiedliche Weise erreichen. Wir können
Unterschiede in der Berechnung von Steuern in diesen beiden Fällen haben. Wir
wollen sicherstellen, dass wir in den Unterklassen Methoden mit gleicher Signatur haben. Das kann bedeuten, die Klasse Disability Billing Scheme zu ändern,
um die Unterklassen besser anordnen zu können. Meistens stellen wir fest, dass
sich mit der wachsenden Zahl von Varianten, die wir bearbeiten, das Muster ähnlicher und variierender Methoden stabilisiert, so dass weitere Varianten einfacher
zu erledigen sind.