Sie haben eine in beiden Richtungen benutzbare Assoziation, aber eine Klasse be- nửtigt die Elemente der anderen nicht mehr.
Entfernen Sie die nicht mehr benửtigte Richtung der Assoziation.
8.8.1 Motivation
Sie haben eine in beiden Richtungen benutzbare Assoziation, aber eine Klasse be- nửtigt die Elemente der anderen nicht mehr.
Bidirektionale Assoziationen sind nützlich, aber sie haben ihren Preis. Der Preis ist die zusọtzliche Komplexitọt, einen beidseitigen Link zu pflegen und sicherzustel- len, dass die Objekte richtig erzeugt und entfernt werden. Bidirektionale Assozia- tionen sind fỹr viele Programmierer etwas Unnatỹrliches, so dass sie eine họufige Quelle von Fehlern sind.
Viele beidseitige Links führen leicht dazu, dass durch Fehler Zombies entstehen:
Objekte, die eigentlich schon entfernt sein sollten, die aber noch existieren, weil eine Referenz nicht gelửscht wurde.
Bidirektionale Assoziationen erzwingen eine Abhọngigkeit zwischen zwei Klas- sen. Jede Änderung der einen Klasse kann eine Änderung der anderen erfordern.
Befinden sich die Klassen in verschiedenen Paketen, so erhalten Sie eine Abhọn- gigkeit zwischen den Paketen. Viele Abhọngigkeiten fỹhren zu einem stark gekop- pelten System, in dem jede kleine Änderung zu einer Fülle unvorhersehbarer Ver- zweigungen führt.
Order Customer
1
Order Customer
➾ 1
∗
∗
Sie sollten bidirektionale Assoziationen verwenden, wenn Sie sie benửtigen, aber andernfalls nicht. Sobald Sie eine bidirektionale Assoziation sehen, die sich nicht mehr lohnt, entfernen Sie die unnửtige Richtung.
8.8.2 Vorgehen
Sie haben eine in beiden Richtungen benutzbare Assoziation, aber eine Klasse be- nửtigt die Elemente der anderen nicht mehr.
• Untersuchen Sie alle Leser des Feldes, das den Zeiger enthọlt, den Sie entfernen wollen, und prüfen Sie, ob er entfernt werden kann.
Betrachten Sie die direkten Leser und weitere Methoden, die diese Methoden auf- rufen.
Untersuchen Sie, ob es mửglich ist, das Objekt ohne den Zeiger zu erreichen. Wenn ja, kửnnen Sie Algorithmus ersetzen (136) auf die get-Methode anwenden, um es Clients zu ermửglichen, die get-Methode zu verwenden, auch wenn es keinen Zei- ger gibt.
Erwọgen Sie, das Objekt als Parameter an alle Methoden zu ỹbergeben, die das Feld benutzen.
• Wenn Clients die get-Methode benửtigen, verwenden Sie Eigenes Feld kapseln (171), führen Algorithmus ersetzen (136) für die get-Methode durch, wandeln um und testen.
• Wenn Clients die get-Methode nicht benửtigen, so ọndern Sie alle Clients des Feldes so, dass sie das Objekt in dem Feld auf andere Weise bekommen. Wan- deln Sie nach jeder Änderung um und testen Sie.
• Wenn es keinen Leser des Feldes mehr gibt, entfernen Sie alle Änderungen des Feldes und entfernen das Feld.
Gibt es viele Stellen, an denen das Feld zugewiesen wird, so verwenden Sie Eigenes Feld kapseln (171), damit alle eine einzige set-Methode verwenden. Wandeln Sie um und testen Sie. Wenn das funktioniert, entfernen Sie das Feld, die set-Methode und alle Aufrufe der set-Methode.
• Wandeln Sie um und testen Sie.
➾
➾
➾
➾
8.8.3 Beispiel
Sie haben eine in beiden Richtungen benutzbare Assoziation, aber eine Klasse be- nửtigt die Elemente der anderen nicht mehr.
Ich beginne dort, wo ich in dem Beispiel in Gerichtete Assoziation durch bidirektio- nale ersetzen (199) aufgehửrt habe.
class Order...
Customer getCustomer() { return _customer;
}
void setCustomer (Customer arg) {
if (_customer != null) _customer.friendOrders().remove(this);
_customer = arg;
if (_customer != null) _customer.friendOrders().add(this);
}
private Customer _customer;
class Customer...
void addOrder(Order arg) { arg.setCustomer(this);
}
private Set _orders = new HashSet();
Set friendOrders() {
/** should only be used by Order */
return _orders;
}
Ich habe festgestellt, dass ich in meiner Anwendung keine Auftrọge (Order) habe, wenn ich nicht bereits einen Kunden (Customer) habe, also mửchte ich die Verbin- dung von Auftrag zu Kunde lửsen.
Der schwierigste Teil dieser Refaktorisierung besteht darin zu prüfen, ob ich dies machen kann. Wenn ich weiò, dass es gefahrlos mửglich ist, ist diese Refaktorisie- rung einfach. Die Frage ist, welcher Code sich auf das Feld _customer verlọsst. Um das Feld zu entfernen, benửtige ich eine Alternative.
Mein erster Schritt besteht darin, alle Leser des Feldes und alle Methoden zu un- tersuchen, die diese Leser aufrufen. Kann ich einen anderen Weg finden, das Kun- denobjekt zu liefern? Oft heiòt das, das Kundenobjekt als Parameter an eine Me- thode zu übergeben. Hier sehen Sie ein stark vereinfachtes Beispiel hierfür:
class Order...
double getDiscountedPrice() {
return getGrossPrice() * (1 – _customer.getDiscount());
} wird zu:
class Order...
double getDiscountedPrice(Customer customer) {
return getGrossPrice() * (1 – customer.getDiscount());
}
Das funktioniert besonders gut, wenn das Verhalten von Customer aus aufgerufen wird, weil dieses Objekt sich dann leicht selbst als Argument übergeben kann.
Also wird
class Customer...
double getPriceFor(Order order) {
Assert.isTrue(_orders.contains(order)); // siehe Zusicherung einführen (273)
return order.getDiscountedPrice();
zu:
class Customer...
double getPriceFor(Order order) {
Assert.isTrue(_orders.contains(order));
return order.getDiscountedPrice(this);
}
Eine andere Alternative, die ich erwọge, besteht darin, die get-Methode so zu ọn- dern, dass sie an den Kunden herankommt, ohne das Feld zu benutzen. Um das zu tun, kann ich Algorithmus ersetzen (136) auf den Rumpf von Order.getCustomer anwenden. Ich kửnnte beispielsweise so etwas tun:
Customer getCustomer() {
Iterator iter = Customer.getInstances().iterator();
while (iter.hasNext()) {
Customer each = (Customer)iter.next();
if (each.containsOrder(this)) return each;
}
return null;
}
Das ist langsam, aber es funktioniert. Im Zusammenhang mit einer Datenbank muss es nicht einmal langsam sein, wenn ich eine Datenbankabfrage verwende.
Wenn die Klasse Order Methoden enthọlt, die das Feld _customer verwenden, kann ich sie getCustomer benutzen lassen, indem ich Eigenes Feld kapseln (171) ver- wende.
Wenn ich die get-Methode beibehalte, ist die Assoziation in der Schnittstelle wei- terhin bidirektional, aber in der Implementierung unidirektional. Ich entferne die Rỹckverweise, behalte aber die Abhọngigkeit zwischen den beiden Klassen.
Wenn ich die get-Methode ersetze, so ersetze ich nur diese und hebe mir den Rest fỹr spọter auf. Im anderen Fall ọndere ich jeweils einen Aufrufer, um den Customer aus einer anderen Quelle zu beziehen. Nach jeder Änderung wandle ich um und teste. In der Praxis geht das sehr schnell. Wọre es sehr kompliziert, wỹrde ich diese Refaktorisierung aufgeben.
Sie haben eine in beiden Richtungen benutzbare Assoziation, aber eine Klasse be- nửtigt die Elemente der anderen nicht mehr.
Nachdem ich alle Leser des Felds eliminiert habe, kann ich mich mit den Metho- den beschọftigen, die das Feld ọndern. Dies besteht einfach darin, alle Zuweisun- gen zu diesem Feld zu entfernen und dann das Feld zu entfernen. Da es keiner mehr liest, sollte das keine Rolle spielen. Sie haben eine in beiden Richtungen be- nutzbare Assoziation, aber eine Klasse benửtigt die Elemente der anderen nicht mehr.