Chaque fois que je le peux, j'essaie de restreindre la communication entre les objets à un modèle de demande et réponse. Il y a un ordre partiel implicite sur les objets dans mon programme de telle sorte qu'entre deux objets A et B, il puisse y avoir un moyen pour A d'appeler directement ou indirectement une méthode de B ou pour B d'appeler directement ou indirectement une méthode de A , mais il n'est jamais possible pour A et B d'appeler mutuellement leurs méthodes. Parfois, bien sûr, vous voulez avoir une communication en amont avec l'appelant d'une méthode. Il y a plusieurs façons que j'aime faire cela, et ni l'un ni l'autre ne sont des rappels.
Une façon consiste à inclure plus d'informations dans la valeur de retour de l'appel de méthode, ce qui signifie que le code client doit décider quoi en faire une fois que la procédure lui a rendu le contrôle.
L'autre façon consiste à appeler un objet enfant mutuel. Autrement dit, si A appelle une méthode sur B et que B a besoin de communiquer des informations à A, B appelle une méthode sur C, où A et B peuvent tous deux appeler C, mais C ne peut pas appeler A ou B. L'objet A serait alors chargé d'obtenir les informations de C après que B retourne le contrôle à A. Notez que ce n'est pas vraiment fondamentalement différent de la première façon que j'ai proposée. L'objet A ne peut toujours récupérer les informations qu'à partir d'une valeur de retour; aucune des méthodes de l'objet A n'est invoquée par B ou C. Une variante de cette astuce consiste à passer C comme paramètre à la méthode, mais les restrictions sur la relation de C à A et B s'appliquent toujours.
Maintenant, la question importante est pourquoi j'insiste pour faire les choses de cette façon. Il y a trois raisons principales:
- Il garde mes objets plus lâchement couplés. Mes objets peuvent encapsuler d'autres objets, mais ils ne dépendront jamais du contexte de l'appelant et le contexte ne dépendra jamais des objets encapsulés.
- Il garde mon flux de contrôle facile à raisonner. C'est agréable de pouvoir supposer que le seul code qui peut changer l'état interne
self
pendant l'exécution d'une méthode est cette méthode et aucune autre. C'est le même genre de raisonnement qui pourrait conduire à mettre des mutex sur des objets concurrents.
- Il protège les invariants sur les données encapsulées de mes objets. Les méthodes publiques sont autorisées à dépendre des invariants, et ces invariants peuvent être violés si une méthode peut être appelée en externe alors qu'une autre est déjà en cours d'exécution.
Je ne suis pas contre toutes les utilisations des rappels. Conformément à ma politique de ne jamais "appeler l'appelant", si un objet A appelle une méthode sur B et lui passe un rappel, le rappel ne peut pas changer l'état interne de A, et cela inclut les objets encapsulés par A et le objets dans le contexte de A. En d'autres termes, le rappel ne peut appeler que des méthodes sur des objets qui lui sont donnés par B. Le rappel, en effet, est soumis aux mêmes restrictions que B.
Une dernière extrémité lâche à rattacher est que je permettrai l'invocation de toute fonction pure, indépendamment de cet ordre partiel dont j'ai parlé. Les fonctions pures sont un peu différentes des méthodes dans la mesure où elles ne peuvent pas changer ou dépendre d'un état mutable ou d'effets secondaires, donc ne vous inquiétez pas de les confondre.