Subclassing a collection of interdependent classes in Objective-C and providing type safety

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 { //bla } 

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: (]

+4
source share
2 answers

Solution quick and dirty:

After re-analyzing my class structure and looking for several previously unforeseen consequences on potential traps in the future development of my planned class of graph classes, I came to the conclusion that it fits quite a lot with my proposed solution 4, but I accompany it with some major restructuring (see the attached simplified diagram ER). My plan is, instead of the heavy multipurpose ΓΌber classes, it has several target components that (if well built) can be combined into various types of special purpose > (and relatively light) toolbox .

Potential traps of simple inheritance:

If I implement dimensional sets of functions in a subclass of MyGenericGraph , then this makes it impossible for me to simply create more specific subclasses of the graph (for example, for specialized trees), which can be either lightweight and general (for example, MyGenericGraph ) or size (for example, My3DGraph ). For the MyGenericTree class (e.g. for tree analysis) I need a subclass of MyGenericGraph . For My3DTree (e.g. tree view) I need a subclass of My3DGraph . My3DTree might not inherit any logic from MyGenericTree . I would implement dimensional functions redundantly. Poorly. Pretty bad.

Suggested architecture class structures:

  • Completely get rid of any "dimensional flavored" classes . Store classes of strict-barebone data structures with only the basic and necessary logic.

  • Imagine the MyVertex class, which provides dimensional attributes and methods for nodes, if necessary (by adding MyVertex *vertex ivar to MyGraphNode (default is nil)). It also makes it much easier to reuse them in MyVertexCloud , a simple point cloud container that should come in handy to improve my force- MyVertexCloud layout algorithm.

  • Delegate any logic that is not strictly essential for the data structure of the graphs for special helper classes . As such, MyGraphNodeClusterRelaxer will be responsible for any logic specific to the visual graphic layout.

  • The MyGraph subclass will be quick and easy thanks to my unidirectional inheritance and modularity chain.

  • Using an external MyGraphNodeClusterRelaxer will also allow me to weaken a subset of graph nodes , not just the whole graph, as My3DGraph would do.

  • MyGraphNodeCluster will be nothing more than a wrapper for a basically set of nodes (just one graph). Subclasses of this may be more specific in the criteria and algorithms of cluster membership.

  • Getting MyGraphNodeCluster from the entire graph will be as easy as a call (MyGraphNodeCluster *)[myGraph nodeCluster]; , and from there you will get MyVertexCloud via (MyVertexCloud *)[myNodeCluster vertexCloud]; . The opposite (for the latter) is impossible for valid reasons.

(simplified) Relational entity model:

enter image description here

+1
source

Personally, I would not use any of them. I was going to offer something similar to your second solution as an alternative, but I think this is the best way:

Declare addNode as - (void)addNode:(MyGenericGraphNode *)node . Then, in the implementation, make sure that it is the correct class. This is an even better choice, as you mentioned in the problems with section 1 of the solution that you want the 3D graph to also handle common nodes. I don’t know how you want to deal with them, but you may find that the new node was not 3D and created a new 3D node from it, for example, simply setting the z coordinate for z on everything.

Example:

 //My3DGraph implementation - (void)addNode:(MyGenericGraphNode *)node { if(![node isKindOfClass:[My3DGraphNode class]]) node = [My3DGraphNode nodeWithNode:node]; //add node } //My3DGraphNode class + (My3DGraphNode *)nodeWithNode:(MyGenericGraphNode *)otherNode { //Make sure you can create a 3D node from otherNode //Change 2D properties of otherNode to 3D properties, create new node with those properties } 
0
source

Source: https://habr.com/ru/post/885934/


All Articles