Diagnoseproblem bei der Überprüfung von Voraussetzungen
-
Situation ist die folgende:
class Node { public: virtual bool canConnectTo (Node* anotherNode); void connectTo (Node* anotherNode); };
Was da passieren soll, ist leicht ersichtlich: canConnectTo() überprüft die Gültigkeit der einzugehenden Verbindung (in meinem Fall habe ich beispielsweise einen gerichteten azyklischen Graphen und möchte folgerichtig Zyklen verhindern), und connectTo() ruft zunächst canConnectTo() auf und wirft im Bedarfsfall eine Exception.
Das Problem ist nun, daß diese Exception nicht aussagekräftig ist, denn connectTo() weiß nicht, warum die Verbindung nicht möglich ist.
Ein naheliegender Ansatz zur Lösung wäre, connectTo() gleichfalls virtuell zu machen und alle Überprüfungen, die in canConnectTo durchgeführt werden, erneut durchzuführen; das finde ich aber aufgrund der Codeduplizierung nicht schön.
Zurzeit löse ich das Problem so:
struct Node { protected: virtual bool canConnectTo (Node* anotherNode, bool throwIfNot); public: bool canConnectTo (Node* anotherNode); void connectTo (Node* anotherNode); }; bool canConnectTo (Node* anotherNode) { return canConnectTo (anotherNode, false); } void connectTo (Node* anotherNode) { canConnectTo (anotherNode, true); ... }
Dann kann direkt bei der Überprüfung in canConnectTo() eine aussagekräftige Exception geworfen werden. Das ist eine sehr pragmatische Lösung, an der mir lediglich die Funktion canConnectTo() mißfällt, die damit zu einem etwas häßlichen Hybriden geworden ist und zweierlei Zwecke erfüllt.
Hat jemand einen schöneren Vorschlag?
-
Problem hat sich gelöst. Mir fiel auf, daß ich doch etwas mehr Flexibilität brauche als einfach eine Exception; ich möchte auch etwa in einem interaktiven Graphen-Designer ein dem Grund entsprechendes Cursor-Symbol anzeigen können. Die Lösung sieht so aus (und läßt sich nicht direkt in C++ übersetzen, daher in der Originalsprache Delphi):
type TConnectionFailure = class public class function Reason: String; virtual; abstract; class function CursorOverlay: TCursor; virtual; abstract; end; TCycleConnectionFailure = class (TConnectionFailure) ... end; TNode = class public function CanConnectTo (ANode: TNode; out Reason: TConnectionFailureClass): Boolean; overload; virtual; function CanConnectTo (ANode: TNode): Boolean; overload; function ConnectTo (ANode: TNode); end; function TNode.CanConnectTo (ANode: TNode; out Reason: TConnectionFailureClass): Boolean; begin Result := not ConnectionIntroducesCycle (ANode); if not Result then Reason := TCycleConnectionFailure; end; function TNode.CanConnectTo (ANode: TNode): Boolean; var Reason: TConnectionFailureClass; begin Result := CanConnectTo (ANode, Reason); end; function TNode.ConnectTo (ANode: TNode); var Reason: TConnectionFailureClass; begin if not CanConnectTo (ANode, Reason) then raise Exception.CreateFmt ('Cannot connect %s to %s: %s', [Self.ToString, ANode.ToString, Reason.Reason]); ... end;