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.
8.15.1 Motivation
Diese Refaktorisierung ọhnelt Typenschlỹssel durch Unterklassen ersetzen (227), sie kann aber auch eingesetzt werden, wenn sich der Typenschlỹssel wọhrend des Le- bens eines Objekts ọndert oder ein anderer Grund die Verwendung von Unter- klassen ausschlieòt. Sie verwendet das Zustands- oder das Strategiemuster [Gang of Four].
Zustand und Strategie sind sehr ọhnlich, die Refaktorisierung ist daher die glei- che, welches Muster Sie auch wọhlen, und es ist wirklich nicht so wichtig. Wọh- len Sie das Muster, das auf die jeweiligen Verhọltnisse am besten passt. Wenn Sie versuchen, einen einzelnen Algorithmus mittels Bedingten Ausdruck durch Polymor- phismus 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 Ty- penschlü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.
ENGINEER : int SALESMAN : int type : int
Employee
Employee Type
Engineer Salesman
Employee 1
➾
➾
• Erstellen Sie eine abstrakte Abfrage in dem Zustandsobjekt, um den Typen- schlüssel zu liefern. Erstellen Sie eine überschreibende Abfrage in jeder Unter- klasse, 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 zuzuwei- sen.
• 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");
} }
Ich unterstelle, dass dies eine aufregende und besonders fortschrittliche Firma ist, die die Befửrderung von Managern zu Ingenieuren (Engineer) zulọsst. Der Typen- schlỹssel ist also verọnderbar, und ich kann keine Unterklassen verwenden. Mein erster Schritt ist also wie immer, das Typenschlüsselfeld zu kapseln (Eigenes Feld kapseln (171)):
Employee (int type) { setType (type);
}
int getType() { return _type;
}
void setType(int arg) { _type = arg;
}
int payAmount() { switch (getType()) { case ENGINEER:
return _monthlySalary;
case SALESMAN:
return _monthlySalary + _commission;
case MANAGER:
return _monthlySalary + _bonus;
default:
throw new RuntimeException("Incorrect Employee");
} }
Nun deklariere ich die Zustandsklasse. Ich deklariere sie als abstrakte Klasse und stelle eine abstrakte Methode zur Verfügung, um den Typenschlüssel zu liefern:
abstract class EmployeeType { abstract int getTypeCode();
}
Nun kann ich die Unterklassen erstellen:
class Engineer extends EmployeeType { int getTypeCode () {
return Employee.ENGINEER;
} }
class Manager extends EmployeeType { int getTypeCode () {
return Employee.MANAGER;
} }
class Salesman extends EmployeeType { int getTypeCode () {
return Employee.SALESMAN;
} }
Was ich bisher habe, wandle ich nun um, und alles ist so trivial, dass sogar ich es einfach umwandeln kann. Nun verknỹpfe ich die Unterklassen tatsọchlich mit Employee, indem ich die Zugriffsmethoden auf den Typenschlỹssel ọndere:
class Employee...
private EmployeeType _type;
int getType() {
return _type.getTypeCode();
}
void setType(int arg) { switch (arg) { case ENGINEER:
_type = new Engineer();
break;
case SALESMAN:
_type = new Salesman();
break;
case MANAGER:
_type = new Manager();
break;
default:
throw new IllegalArgumentException("Incorrect Employee Code");
} }
Das heiòt, ich habe hier nun einen switch-Befehl. Wenn ich mit der Refaktorisie- rung fertig bin, wird dies der einzige im ganzen Code sein, und er wird nur ausge- fỹhrt, wenn sich der Typ ọndert. Ich kann auch Konstruktor durch Fabrikmethode er- setzen (313) anwenden, um Fabrikmethoden fỹr die verschiedenen Fọlle zu schaffen. Ich kann alle anderen switch-Befehle schnell durch Bedingten Ausdruck durch Polymorphismus ersetzen (259) eliminieren.
Ich mửchte Typenschlỹssel durch Zustand/Strategie ersetzen (231) beenden, indem ich alles Wissen über Typenschlüssel und Unterklassen in die neue Klasse ver- schiebe. Zunọchst kopiere ich alle Typenschlỹsseldefinitionen in die Employee- Type-Klasse, erstelle eine Fabrikmethode für EmployeeType und passe die set-Me- thode von Employee an:
class Employee...
void setType(int arg) {
_type = EmployeeType.newType(arg);
}
class EmployeeType...
static EmployeeType newType(int code) { switch (code) {
case ENGINEER:
return new Engineer();
case SALESMAN:
return new Salesman();
case MANAGER:
return new Manager();
default:
throw new IllegalArgumentException("Incorrect Employee Code");
} }
static final int ENGINEER = 0;
static final int SALESMAN = 1;
static final int MANAGER = 2;
Dann entferne ich die Typenschlüsseldefinitionen aus der Klasse Employee und er- setze sie durch Referenzen auf EmployeeType:
class Employee...
int payAmount() { switch (getType()) {
case EmployeeType.ENGINEER:
return _monthlySalary;
case EmployeeType.SALESMAN:
return _monthlySalary + _commission;
case EmployeeType.MANAGER:
return _monthlySalary + _bonus;
default:
throw new RuntimeException("Incorrect Employee");
} }
Ich bin nun in der Lage, Bedingten Ausdruck durch Polymorphismus ersetzen (259) auf payAmount anzuwenden.