Wann immer ich kann, versuche ich, die Kommunikation zwischen Objekten auf ein Anforderungs- und Antwortmodell zu beschränken. Es gibt eine implizite Teilreihenfolge für die Objekte in meinem Programm, so dass zwischen zwei beliebigen Objekten A und B eine Möglichkeit für A besteht, eine Methode von B direkt oder indirekt aufzurufen, oder für B, eine Methode von A direkt oder indirekt aufzurufen , aber es ist A und B niemals möglich, sich gegenseitig die Methoden aufzurufen. Manchmal möchten Sie natürlich eine Rückwärtskommunikation mit dem Aufrufer einer Methode haben. Es gibt ein paar Möglichkeiten, wie ich das gerne mache, und keine davon ist ein Rückruf.
Eine Möglichkeit besteht darin, mehr Informationen in den Rückgabewert des Methodenaufrufs aufzunehmen. Dies bedeutet, dass der Clientcode entscheiden kann, was damit geschehen soll, nachdem die Prozedur die Kontrolle an ihn zurückgegeben hat.
Die andere Möglichkeit besteht darin, ein gemeinsames untergeordnetes Objekt aufzurufen. Das heißt, wenn A eine Methode für B aufruft und B einige Informationen an A übermitteln muss, ruft B eine Methode für C auf, wobei A und B beide C aufrufen können, C jedoch A oder B nicht aufrufen kann. Objekt A wäre dann verantwortlich dafür, dass die Informationen von C abgerufen werden, nachdem B die Kontrolle an A zurückgegeben hat. Beachten Sie, dass dies nicht wirklich grundlegend anders ist als der erste von mir vorgeschlagene Weg. Objekt A kann die Informationen immer noch nur von einem Rückgabewert abrufen. Keine der Methoden von Objekt A wird von B oder C aufgerufen. Eine Variation dieses Tricks besteht darin, C als Parameter an die Methode zu übergeben, aber die Einschränkungen für die Beziehung von C zu A und B gelten weiterhin.
Die wichtige Frage ist nun, warum ich darauf bestehe, die Dinge so zu machen. Es gibt drei Hauptgründe:
- Es hält meine Objekte lockerer gekoppelt. Meine Objekte können andere Objekte kapseln, aber sie hängen niemals vom Kontext des Aufrufers ab, und der Kontext hängt niemals von den gekapselten Objekten ab.
- Es hält meinen Kontrollfluss leicht zu überlegen. Es ist schön anzunehmen, dass der einzige Code, der den internen Status
self
während der Ausführung einer Methode ändern kann , diese eine und keine andere Methode ist. Dies ist die gleiche Art von Argumentation, die dazu führen könnte, dass Mutexe auf gleichzeitige Objekte gesetzt werden.
- Es schützt Invarianten auf den gekapselten Daten meiner Objekte. Öffentliche Methoden dürfen von Invarianten abhängen, und diese Invarianten können verletzt werden, wenn eine Methode extern aufgerufen werden kann, während eine andere bereits ausgeführt wird.
Ich bin nicht gegen alle Verwendungszwecke von Rückrufen. In Übereinstimmung mit meiner Richtlinie, niemals den Aufrufer aufzurufen, ändert der Rückruf möglicherweise nicht den internen Status von A, wenn ein Objekt A eine Methode für B aufruft und einen Rückruf an B übergibt, und dies schließt die von A und dem gekapselten Objekte ein Objekte im Kontext von A. Mit anderen Worten, der Rückruf kann nur Methoden für Objekte aufrufen, die ihm von B gegeben wurden. Der Rückruf unterliegt praktisch denselben Einschränkungen wie B.
Ein letztes loses Ende ist, dass ich den Aufruf jeder reinen Funktion erlauben werde, unabhängig von dieser Teilreihenfolge, über die ich gesprochen habe. Reine Funktionen unterscheiden sich ein wenig von Methoden darin, dass sie sich nicht ändern können oder von veränderlichen Zuständen oder Nebenwirkungen abhängen. Sie müssen sich also keine Sorgen machen, dass sie die Dinge verwirren.