Tải bản đầy đủ
14 Typenschlüssel durch Unterklassen ersetzen

14 Typenschlüssel durch Unterklassen ersetzen

Tải bản đầy đủ

Sandini Bib
228

8 Daten organisieren

Der einfachste Weg, diese Struktur zu erstellen, ist Typenschlüssel durch Unterklassen ersetzen. Sie nehmen die Klasse mit dem Typenschlüssel und erstellen eine Unterklasse für jeden Typenschlüssel. Es gibt aber Fälle, in denen Sie dies nicht tun
können. In dem einen Fall ändert sich der Typenschlüssel eines Objekts, nachdem es erstellt wurde. Im anderen wurden bereits Unterklassen der Klasse mit
dem Typenschlüssel gebildet. In beiden Fällen müssen Sie Typenschlüssel durch Zustand/Strategie ersetzen (231) anwenden.
Typenschlüssel durch Unterklassen ersetzen liefert vor allem ein Gerüst, das die Verwendung von Bedingten Ausdruck durch Polymorphismus ersetzen (259) ermöglicht.
Der Auslöser, Typenschlüssel durch Unterklassen zu ersetzen (227) anzuwenden, ist
die Anwesenheit bedingter Befehle. Gibt es keine bedingten Befehle, ist Typenschlüssel durch Klasse ersetzen (221) die weniger kritische Änderung.
Ein anderer Grund dafür, Typenschlüssel durch Unterklassen ersetzen (227) anzuwenden, sind Elemente, die nur für Objekte mit bestimmten Typenschlüsseln relevant sind. Nachdem Sie diese Refaktorisierung durchgeführt haben, können Sie
Methode nach unten verschieben (331) und Feld nach unten verschieben (339) einsetzen, um klarzustellen, dass diese Elemente nur in bestimmten Fällen relevant
sind.
Der Vorteil von Typenschlüssel durch Unterklassen ersetzen (227) ist, dass so das Wissen über Verhaltensvarianten von den Clients in die Klasse wandert. Wenn ich
neue Varianten hinzufüge, muss ich nur eine weitere Unterklasse hinzufügen.
Ohne Polymorphismus müsste ich alle Bedingungen suchen und diese ändern.
Diese Refaktorisierung ist also besonders nützlich, wenn sich die Varianten laufend ändern.

8.14.2


Vorgehen

Kapseln Sie den Typenschlüssel als eigenes Feld (Eigenes Feld kapseln (171)).

➾ Wenn der Typenschlüssel dem Konstruktor übergeben wird, müssen Sie Konstruktor durch Fabrikmethode ersetzen (313) verwenden.


Erstellen Sie für jeden Wert des Typenschlüssels eine Unterklasse. Überschreiben Sie jeweils die get-Methode für den Typenschlüssel, um den relevanten
Wert zu liefern.

➾ Dieser Wert wird direkt angegeben (z.B. return 1). Das sieht schlimm aus, ist
aber nur eine temporäre Maßnahme, bis alle switch-Befehle entfernt worden sind.

Sandini Bib
8.14 Typenschlüssel durch Unterklassen ersetzen

229



Wandeln Sie nach jeder Ersetzung eines Typenschlüsselwerts durch eine Unterklasse um und testen Sie.



Entfernen Sie das Typenschlüsselfeld aus der Oberklasse. Deklarieren Sie die
Zugriffsmethoden für den Typenschlüssel als abstrakt.



Wandeln Sie um und testen Sie.

8.14.3

Beispiel

Ich verwende das langweilige und unrealistische Beispiel von Gehaltszahlungen
an Mitarbeiter (Employee):
class Employee...
private int _type;
static final int ENGINEER = 0;
static final int SALESMAN = 1;
static final int MANAGER = 2;
Employee (int type) {
_type = type;
}

Der erste Schritt besteht darin, Eigenes Feld kapseln (171) auf den Typenschlüssel
_type anzuwenden.
int getType() {
return _type;
}

Da der Konstruktor von Employee den Typenschlüssel als Parameter verwendet,
muss ich ihn durch eine Fabrikmethode ersetzen:
Employee create(int type) {
return new Employee(type);
}
private Employee (int type) {
_type = type;
}

Nun kann ich mit Engineer als einer Unterklasse beginnen.
class Engineer extends Employee {
int getType() {

Sandini Bib
230

8 Daten organisieren

return Employee.ENGINEER;
}
}

Ich muss auch die Fabrikmethode anpassen, um das entsprechende Objekt zu liefern:
class Employee
static Employee create(int type) {
if (type == ENGINEER) return new Engineer();
else return new Employee(type);
}

So mache ich Stück für Stück weiter, bis alle Typenschlüssel durch Unterklassen
ersetzt sind. An dieser Stelle kann ich mich des Feldes _type in der Klasse Employee
entledigen und getType zu einer abstrakten Methode machen. Nun sieht die Fabrikmethode so aus:
abstract int getType();
static Employee create(int type) {
switch (type) {
case ENGINEER:
return new Engineer();
case SALESMAN:
return new Salesman();
case MANAGER:
return new Manager();
default:
throw new IllegalArgumentException("Incorrect type code value");
}
}

Natürlich ist das ein switch-Befehl, den ich lieber vermeiden würde. Aber es ist
nur einer und er wird nur bei der Erstellung von Objekten verwendet.
Nachdem Sie die Unterklassen erstellt haben, sollten Sie natürlich Methode nach
unten verschieben (337) und Feld nach unten verschieben (339) auf alle Methoden
und Felder anwenden, die nur für eine bestimmte Unterklasse von Employee relevant sind.

Sandini Bib
8.15 Typenschlüssel durch Zustand/Strategie ersetzen

8.15

231

Typenschlüssel durch Zustand/Strategie ersetzen

Sie haben einen Typenschlüssel, der das Verhalten einer Klasse beeinflusst, aber
Sie können ihn nicht durch Unterklassen ersetzen.
Ersetzen Sie den Typenschlüssel durch ein Zustandsobjekt.

Employee
Employee
ENGINEER : int
SALESMAN : int
type : int

1


Engineer

8.15.1

Employee Type

Salesman

Motivation

Diese Refaktorisierung ähnelt Typenschlüssel durch Unterklassen ersetzen (227), sie
kann aber auch eingesetzt werden, wenn sich der Typenschlüssel während des Lebens eines Objekts ändert oder ein anderer Grund die Verwendung von Unterklassen ausschließt. Sie verwendet das Zustands- oder das Strategiemuster [Gang
of Four].
Zustand und Strategie sind sehr ähnlich, die Refaktorisierung ist daher die gleiche, welches Muster Sie auch wählen, und es ist wirklich nicht so wichtig. Wählen Sie das Muster, das auf die jeweiligen Verhältnisse am besten passt. Wenn Sie
versuchen, einen einzelnen Algorithmus mittels Bedingten Ausdruck durch Polymorphismus ersetzen (259) zu vereinfachen, ist Strategie die bessere Wahl. Wenn Sie
zustandsspezifische Daten verschieben und sich das Objekt als sich ändernden
Zustand vorstellen, so verwenden Sie das Zustandsmuster.

8.15.2

Vorgehen



Kapseln Sie den Typenschlüssel als eigenes Feld (Eigenes Feld kapseln (171)).



Erstellen Sie eine neue Klasse, und benennen Sie sie nach der Aufgabe des Typenschlüssels. Dies ist das Zustandsobjekt.



Erstellen Sie Unterklassen des Zustandsobjekts, eine für jeden Typenschlüssel.

➾ Es ist einfacher, alle Unterklassen auf einmal hinzuzufügen als jede einzeln.

Sandini Bib
232

8 Daten organisieren



Erstellen Sie eine abstrakte Abfrage in dem Zustandsobjekt, um den Typenschlüssel zu liefern. Erstellen Sie eine überschreibende Abfrage in jeder Unterklasse, um den korrekten Typenschlüssel zu liefern.



Wandeln Sie um.



Erstellen Sie ein Feld in der alten Klasse für das neue Zustandsobjekt.



Lassen Sie die Abfrage des Typenschlüssels in der Originalklasse die Abfrage an
das Zustandsobjekt delegieren.



Passen Sie die Methoden zum Setzen des Typenschlüssels in der Originalklasse
an, um eine Instanz der jeweiligen Unterklasse des Zustandsobjekts zuzuweisen.



Wandeln Sie um und testen Sie.

8.15.3

Beispiel

Ich verwende wieder das ermüdende und einfallslose Beispiel der Gehaltszahlung
an Mitarbeiter (Employee):
class Employee {
private int _type;
static final int ENGINEER = 0;
static final int SALESMAN = 1;
static final int MANAGER = 2;
Employee (int type) {
_type = type;
}

Hier folgt ein Beispiel bedingten Verhaltes, das diesen Code nutzt:
int payAmount() {
switch (_type) {
case ENGINEER:
return _monthlySalary;
case SALESMAN:
return _monthlySalary + _commission;
case MANAGER:
return _monthlySalary + _bonus;
default:
throw new RuntimeException("Incorrect Employee");
}
}