Sie haben eine Bedingung, die unterschiedliches Verhalten auswọhlt, je nachdem, welchen Typ ein Objekt hat.
Verschieben Sie jeden Zweig in eine überschreibende Methode einer Unterklasse. Dekla- rieren Sie die Originalmethode als abstrakt.
double getSpeed() { switch (_type) { case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() – getLoadFactor() * _numberOfCoconuts;
case NORWEGIAN_BLUE:
return (_isNailed) ? 0 : getBaseSpeed(_voltage);
}
throw new RuntimeException ("Should be unreachable");
} ➾
getSpeed Bird
getSpeed European
getSpeed African
getSpeed Norwegian Blue
9.6.1 Motivation
Eines der am tollsten klingenden Wửrter im Objektjargon ist Polymorphismus. Das Wesentliche am Polymorphismus ist, dass Sie es mit seiner Hilfe vermeiden kửn- nen, explizite Bedingungen zu schreiben, wenn Sie Objekte haben, deren Verhal- ten in Abhọngigkeit von ihrem Typ variiert.
Als ein Ergebnis werden Sie sehen, dass switch-Befehle mit Typenschlüsseln oder if-then-else-Befehle mit Typenstrings in objektorientierten Programmen viel weniger gebrọuchlich sind.
Der Polymorphismus bietet Ihnen viele Vorteile. Den grửòten Gewinn erzielen Sie, wenn der gleiche Satz von Bedingungen an vielen Stellen im Programm vor- kommt. Wollen Sie einen neuen Typ ergọnzen, so mỹssen Sie alle diese Bedingun- gen suchen und ọndern. Aber wenn Sie Unterklassen haben, erstellen Sie nur eine neue Unterklasse mit den entsprechenden Methoden. Clients der Klasse müssen nichts von der Unterklasse wissen, was die Abhọngigkeiten in Ihrem System ver- ringert und es einfacher zu ọndern macht.
9.6.2 Vorgehen
Bevor Sie beginnen kửnnen, mit Bedingung durch Polymorphismus ersetzen (259) zu arbeiten, müssen Sie die notwendige Vererbungsstruktur haben. Vielleicht haben Sie diese Struktur aus früheren Refaktorisierungen. Wenn Sie diese Struktur nicht haben, müssen Sie sie erstellen.
Um die Vererbungsstruktur zu erstellen, haben Sie zwei Optionen: Typenschlüssel durch Unterklassen ersetzen (227) und Typenschlüssel durch Zustand/Strategie ersetzen (231). Unterklassen sind die einfachste Option. Wenn Sie kửnnen, sollten Sie diese nutzen. Verọndern Sie aber den Typenschlỹssel, nachdem das Objekt er- zeugt wurde, so kửnnen Sie nicht spezialisieren und mỹssen das Zustands- oder Strategiemuster einsetzen. Sie müssen das Zustands- oder Strategiemuster auch dann anwenden, wenn Sie die Klasse bereits aus anderen Gründen spezialisieren.
Denken Sie daran, dass Sie auch dann, wenn mehrere switch-Befehle aufgrund des gleichen Typenschlüssels verzweigen, nur eine Vererbungsstruktur für diesen Schlüssel brauchen.
Nun kửnnen Sie sich mit den Bedingungen befassen. Der Code, auf den Sie zielen, kann ein switch- (case-) oder if-Befehl sein.
• Ist der bedingte Ausdruck ein Teil einer grửòeren Methode, so zerlegen Sie ihn und verwenden Methode extrahieren (106).
• Falls notwendig, verwenden Sie Methode verschieben (139), um den bedingten Ausdruck an die Spitze der Vererbungsstruktur zu bringen.
• Greifen Sie eine der Unterklassen heraus. Erstellen Sie eine Unterklasse, die die Methode mit dem bedingten Ausdruck überschreibt. Kopieren Sie diesen Zweig des bedingten Ausdrucks in die Unterklasse, und justieren Sie ihn so, dass er hierhin passt.
Es kann sein, dass Sie hierzu einige private Elemente der Oberklasse als geschützt deklarieren müssen.
• Wandeln Sie um und testen Sie.
• Entfernen Sie den kopierten Zweig aus dem bedingten Ausdruck.
• Wandeln Sie um und testen Sie.
• Wiederholen Sie dies für alle Zweige des bedingten Ausdrucks, bis alle Zweige in Unterklassen verwandelt sind.
• Deklarieren Sie die Methode der Oberklasse als abstrakt.
9.6.3 Beispiel
Ich verwende das langweilige und stark vereinfachte Beispiel der Gehaltszahlung.
Ich verwende die Klassen nach der Anwendung von Typenschlüssel durch Zustand/
Strategie ersetzen (231), so dass die Objekte aussehen wie in Abbildung 9-1. (In dem Beispiel in Kapitel 8 sehen Sie, wie wir dorthin gekommen sind.)
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");
} }
➾
int getType() {
return _type.getTypeCode();
}
private EmployeeType _type;
abstract class EmployeeType...
abstract int getTypeCode();
class Engineer extends EmployeeType...
int getTypeCode() {
return Employee.ENGINEER;
}
//... and other subclasses
Der switch-Befehl ist schon handlich extrahiert, so dass in dieser Richtung nichts zu tun ist. Ich muss ihn in die Klasse EmployeeType verschieben, da dies die Klasse ist, die spezialisiert wird.
class EmployeeType...
int payAmount(Employee emp) { switch (getTypeCode()) { case ENGINEER:
return emp.getMonthlySalary();
case SALESMAN:
return emp.getMonthlySalary() + emp.getCommission();
case MANAGER:
Abbildung 9-1 Die Vererbungsstruktur
Employee Employee Type
Engineer Salesman
Manager _type
1
return emp.getMonthlySalary() + emp.getBonus();
default:
throw new RuntimeException("Incorrect Employee");
} }
Da ich die Daten von Employee benửtige, muss ich ein Employee-Objekt als Parame- ter ỹbergeben. Einige dieser Daten kửnnen vielleicht in das EmployeeType-Objekt verschoben werden, aber das ist ein Thema für eine andere Refaktorisierung.
Wenn sich dies umwandeln lọsst, lasse ich die payAmount-Methode in Employee an die neue Klasse delegieren:
class Employee...
int payAmount() {
return _type.payAmount(this);
}
Nun kann ich mich der Arbeit an den case-Klauseln zuwenden. Das geschieht so ọhnlich, wie kleine Jungen Insekten tửten – ein Bein nach dem anderen ausrei- òen. Als erstes kopiere ich den ENGINEER-Zweig des switch-Befehls in die Klasse En- gineer.
class Engineer...
int payAmount(Employee emp) { return emp.getMonthlySalary();
}
Diese neue Methode überschreibt für Ingenieur-Objekte den gesamten switch-Be- fehl. Da ich paranoid bin, baue ich manchmal eine Falle in den switch-Befehl ein:
class EmployeeType...
int payAmount(Employee emp) { switch (getTypeCode()) { case ENGINEER:
throw new RuntimeException ("Should be being overridden");
case SALESMAN:
return emp.getMonthlySalary() + emp.getCommission();
case MANAGER:
return emp.getMonthlySalary() + emp.getBonus();
default:
throw new RuntimeException("Incorrect Employee");
} }
Dies geht so weiter, bis alle Zweige entfernt sind:
class Salesman...
int payAmount(Employee emp) {
return emp.getMonthlySalary() + emp.getCommission();
}
class Manager...
int payAmount(Employee emp) {
return emp.getMonthlySalary() + emp.getBonus();
}
Nun kann ich die Methode in der Oberklasse als abstrakt deklarieren:
class EmployeeType...
abstract int payAmount(Employee emp);