I implemented the base class of the graph (not like in the "graph", but like in the "network"!), Which will be used for the main theoretical tasks of the graph , (see brief fragments of the header file below)
In addition to universal graphics functions, it also implements functionality for positioning a node in 3D space . And this advanced three-dimensional functionality, which I would like to highlight in a subclass , resulting in:
- weighted general classes
(MyGenericGraph, MyGenericGraphNode, MyGenericGraphEdge) - heavier specialized subclasses
(My3DGraph, My3DGraphNode, My3DGraphEdge)
It is still so good, theoretically, that it is.
Problem:
I would have to guarantee (and preferably at compile time) that you cannot add a generic MyGenericGraphNodes to the spezialized My3DGraph , as it depends heavily on the added 3D logic inside My3DGraphNode . (Although generic MyGenericGraph just doesn't care.)
The main problem is just as simple: I cannot override these methods from MyGenericGraph:
- (void)addNode:(MyGenericGraphNode *)aNode; - (void)removeNode:(MyGenericGraphNode *)aNode;
with these methods in my subclass of My3DGraph:
- (void)addNode:(My3DGraphNode *)aNode; - (void)removeNode:(My3DGraphNode *)aNode;
So far I have come up with three possible solutions, but before heading to any of them, I would like to hear some opinions about them. (and hopefully saves me some unforeseen problems in my way)
I am wondering if there is another and superior solution or design template for this that I am missing? Or if not: which of my decisions would you come to?
I would like to hear your opinion about this.
Possible Solution 1
- Adding the
MyAbstractGraph abstract class , which will basically be identical to the common parts of my current MyGenericGraph implementation (see below), but there will be no node-units / removal methods . MyGenericGraph and My3DGraph then simply be subclasses of MyAbstractGraph . And although MyGenericGraph will only implement the missing node-addition / removal methods, My3DGraph will further implement all the functionality of the 3D space. Both require their respective node class types. (same for MyGenericGraphNode and MyGenericGraphEdge and their 3D counterparts)
Problems with this solution:. add significant complexity to a fairly simple problem .
Moreover, since My3DGraph must deal with My3DGraphNodes AND MyGenericGraphNodes , I would have to implement the MyGenericGraph method as:
- (void)addNode:(MyAbstractGraphNode *)aNode;`
but My3DGraph like:
- (void)addNode:(My3DGraphNode *)aNode;
since otherwise my common graph will not accept 3d nodes. This can lead to an unnecessary abstract class.
Possible Solution 2
True and simple subclasses + moving MyGenericGraphNode/My3DGraphNode directly to MyGenericGraph/My3DGraph to get something like: - (MyGenericGraphNode *)newNode; which selects and returns a node of the correct type and immediately add it to the graph . One could completely get rid of - (void)addNode:(MyGenericGraphNode *)aNode; ,, leaving no possibility to add nodes than from the Graph itself (therefore, guaranteeing proper class membership).
Problems with this solution: While this does not add any noteworthy complexity to classes, it, on the other hand, will basically put me in the same predicament again as soon as I say - I wanted to add functionality to my My3DGraph to move node from one graph to another. And the imho class should be able to deal with the object regardless of who created it and why.
Possible Solution 3
True and simple subclasses + adding a specialized method for three-dimensional nodes and disabling the general method , for example:
- (void)addNode:(MyGenericGraphNode *)aNode { [self doesNotRecognizeSelector:_cmd]; } - (void)add3DNode:(My3DGraphNode *)aNode {
Problems with this solution: The general method [a3DGraph addNode:aNode] will still be displayed in the automatic Xcode completion, will be discreetly transmitted at compilation, but will suddenly throw an exception at startup. Frequent headaches are provided.
Possible Solution 4
True and simple subclasses for my graph and only common classes for node and edge, but with an additional ivar pointer My3DUnit My3DUnit *dimensionalUnit: in the node class (default for nil for MyGraph), which implements all the logic and properties and provides 3D functionality to the node class . My3DUnit can simply be created silently (with the position (0,0,0), for example) and tied to common nodes if they are added to the 3d graph and, therefore, compatibility. And vice versa, if a node with DL3DUnit added to the general graph, it just binds it and adds node.
Header files
Here are the (abridged) headings of my classes:
@interface MyGraph : NSObject { // properties: NSMutableSet *nodes; //... //extended 3D properties: double gravityStrength; //... } // functionality: - (void)addNode:(MyGraphNode *)aNode; - (void)removeNode:(MyGraphNode *)aNode; //... //extended 3D functionality: - (double)kineticEnergy; //... @end @interface MyGraphNode : NSObject { // properties: MyGraph *graph; NSMutableSet *edges; //... //extended 3D properties: My3DVector position; //... } // properties: @property (nonatomic, readonly) MyGraph *graph; @property (nonatomic, readonly) NSSet *edges; @property (nonatomic, readonly) NSSet *neighbors; @property (nonatomic, readonly) NSUInteger degree; //... //extended 3D properties @property (nonatomic, assign) My3DVector position; //... // functionality: - (void)attachToGraph:(MyGraph *)aGraph; - (void)detachFromGraph; - (void)addNeighbor:(MyGraphNode *)aNode; - (void)removeNeighbor:(MyGraphNode *)aNode; - (BOOL)hasNeighbor:(MyGraphNode *)aNode; - (NSSet *)neighbors; - (NSUInteger)degree; //... //extended 3D functionality: - (double)distanceToNode:(DLGraphNode *)aNode; //... @end @interface MyGraphEdge : NSObject { // properties: MyGraphNode *predecessor; MyGraphNode *successor; //... } // properties: @property (nonatomic, readonly) MyGraphNode *predecessor; @property (nonatomic, readonly) MyGraphNode *successor; //... // functionality: - (id)initWithPredecessorNode:(MyGraphNode *)predecessorNode successorNode:(MyGraphNode *)successorNode; + (MyGraphEdge *)edgeWithPredecessorNode:(MyGraphNode *)predecessorNode successorNode:(MyGraphNode *)successorNode; - (BOOL)hasNeighbor:(MyGraphNode *)aNode; - (BOOL)hasSuccessor:(MyGraphNode *)aNode; - (BOOL)hasPredecessor:(MyGraphNode *)aNode; @end
This is basically how my schedule is implemented right now. Obviously, quite a lot exists for this, but you should get this idea.
(As you may have noticed, MyGenericGraphEdge does not currently use 3D space functionality, but in the future it might be, for example, calculating its center point, for example, so I included it here.)
[Edit: added solution 4 inspired by ughoavgfhw; fixed error in solution 1, sorry for this: (]