Sie haben zwei Klassen, die Elemente der jeweils anderen benửtigen, es gibt aber nur eine Verbindung in einer Richtung.
Fügen Sie Rückverweise ein, und aktualisieren Sie beide Verweise in den set-Funktionen.
8.7.1 Motivation
Es kann vorkommen, dass Sie zwei Klassen ursprünglich so entworfen haben, dass die eine die andere referenziert. Nach einer Weile stellen Sie fest, dass ein Client der referenzierten Klasse an die Objekte kommen muss, die auf dieses Objekt ver- weisen. Das bedeutet, dass rỹckwọrts entlang der Verweise navigiert werden muss.
Zeiger sind aber Einbahnstraòen, Sie kửnnen das so nicht machen. Oft kửnnen Sie das Problem umgehen, wenn Sie eine andere Verbindung zu dem Objekt finden.
Dies kann Rechenzeit kosten, aber trotzdem sinnvoll sein, und Sie kửnnen dann eine Methode in der referenzierten Klasse haben, die dieses Verhalten nutzt.
Manchmal geht das aber nicht so einfach, und dann müssen Sie eine gegenseitige Referenzierung aufbauen, die manchmal Rückverweis (Back Pointer) genannt wird. Wenn Rỹckverweise Ihnen noch neu sind, so kửnnen Sie sich leicht in ih- nen verheddern. Nachdem Sie sich an dieses Idiom gewửhnt haben, werden Sie feststellen, dass es nicht allzu kompliziert ist.
Dieses Idiom ist jedoch schwierig genug. Sie sollten deshalb weitere Tests haben, zumindest bis Sie mit dem Idiom vertraut sind. Obwohl ich es meistens nicht für notwendig halte, get-Methoden zu testen, ist dies einer der seltenen Fọlle einer Refaktorisierung, die hierfỹr einen Test erhọlt.
Order Customer
1
Order Customer
1
➾
∗
∗
Diese Refaktorisierung verwendet Rỹckverweise, um Bidirektionalitọt zu imple- mentieren. Andere Techniken wie Verbindungsobjekte erfordern andere Refakto- risierungen.
8.7.2 Vorgehen
• Fügen Sie ein Feld für den Rückverweis (Back Pointer) ein.
• Entscheiden Sie, welche Klasse für die Pflege der Assoziation verantwortlich sein soll.
• Erstellen Sie eine Hilfsmethode in der Klasse, die nicht für die Pflege der Asso- ziation verantwortlich ist. Benennen Sie diese Methode so, dass ihre be- schrọnkte Verwendung klar ersichtlich ist.
• Wenn die bestehende Methode zur Pflege der Assoziation sich in der Klasse be- findet, die nun die Assoziation pflegen soll, ọndern Sie sie, um die Rỹckverwei- se zu pflegen.
• Befindet sich diese Methode in der anderen Klasse, so erstellen Sie eine Metho- de zum Pflegen der Assoziation in der Klasse auf der anderen Seite und rufen diese auf.
8.7.3 Beispiel
Ein einfaches Programm hat eine Klasse Order (Auftrag), die einen Customer (Kun- den) referenziert.
class Order...
Customer getCustomer() { return _customer;
}
void setCustomer (Customer arg) { _customer = arg;
}
Customer _customer;
Die Klasse Customer hat keine Referenz auf Order.
Ich beginne die Refaktorisierung, indem ich zu Customer ein Feld hinzufüge. Ein Kunde kann viele Auftrọge haben, also ist dieses Feld eine Collection. Da ich nicht will, dass ein Customer-Objekt ein Order-Objekt mehrfach in seiner Collection hat, ist die korrekte Collection ein Set:
class Customer {
private Set _orders = new HashSet();
Ich muss nun entscheiden, welche Klasse die Verantwortung für die Assoziation übernimmt. Ich ziehe es vor, eine Klasse diese Verantwortung allein übernehmen zu lassen, denn so befindet sich alle Logik zum Manipulieren der Assoziation an einer Stelle. Mein Entscheidungsprozess lọuft wie folgt ab:
1. Sind beide Objekte Referenzobjekte und handelt es sich um eine 1:*-Assoziati- on, so übernimmt das Objekt, das nur ein anderes referenziert, die Pflege der Assoziation. (Das heiòt, hat ein Kunde viele Auftrọge, so pflegt Order die Asso- ziation.)
2. Ist das eine Objekt eine Komponente des anderen, so sollte das Kompositum die Pflege der Assoziation übernehmen.
3. Sind beide Objekte Referenzobjekte und handelt es sich um eine *:*-Assoziati- on, so ist es egal ob Customer oder Order die Pflege der Assoziation übernimmt.
Da Order die Verantwortung für die Assoziation übernimmt, muss ich eine Hilfs- methode in die Klasse Customer einfügen, die direkten Zugriff auf die Collection _orders ermửglicht. Die Methode setCustomer von Order wird diese verwenden, um beide Zeigermengen zu synchronisieren. Ich verwende den Namen friendOr- ders, um zu kennzeichnen, dass diese Methode nur in diesem speziellen Fall zu verwenden ist. Ich minimiere auch ihre Sichtbarkeit, indem ich sie hửchstens in dem Paket sichtbar mache. Ich muss sie ửffentlich machen, wenn die andere Klasse zu einem anderen Paket gehửrt:
class Customer...
Set friendOrders() {
/** Sollte ausschlieòlich von Order zum Ändern der Assoziation verwendet werden! */
return _orders;
}
Nun ọndere ich die Methode setCustomer so, dass sie auch die Rỹckverweise pflegt:
class Order...
void setCustomer (Customer arg) ...
if (_customer != null) _customer.friendOrders().remove(this);
_customer = arg;
if (_customer != null) _customer.friendOrders().add(this);
}
Der genaue Code in dieser Methode họngt von der Multiplizitọt der Assoziation ab. Wenn es zu jedem Order-Objekt ein Customer-Objekt gibt, kann ich die ĩber- prüfung auf null auslassen, aber ich muss auf ein null-Argument prüfen. Das Grundmuster ist aber immer das Gleiche: Erst fordern Sie das andere Objekt auf, seinen Zeiger auf Sie zu entfernen, setzen Ihren Zeiger auf das neue Objekt und fordern das Objekt dann wieder auf, einen Zeiger auf Sie zu setzen.
Wollen Sie den Link auf dem Weg ỹber Customer verọndern, so rufen Sie die ver- antwortliche Methode von Order auf:
class Customer...
void addOrder(Order arg) { arg.setCustomer(this);
}
Kann ein Order (Auftrag) viele Customer (Kunden) haben, so haben Sie den *:*-Fall, und die Methoden sehen so aus:
class Order... //controlling methods void addCustomer (Customer arg) { arg.friendOrders().add(this);
_customers.add(arg);
}
void removeCustomer (Customer arg) { arg.friendOrders().remove(this);
_customers.remove(arg);
}
class Customer...
void addOrder(Order arg) { arg.addCustomer(this);
}
void removeOrder(Order arg) { arg.removeCustomer(this);
}