Я реализовал базовый класс графов (не как в «построении графиков», а как в «сети»!), Который будет использоваться для базовых теоретических задач по графам . (см. Обобщенные фрагменты заголовочного файла ниже)
В дополнение к общей функциональности он также реализует функциональность для позиционирования узла в трехмерном пространстве . И эту расширенную 3D-функциональность я хотел бы выделить в подкласс , в результате чего:
- облегченные универсальные классы
(MyGenericGraph,
MyGenericGraphNode, MyGenericGraphEdge)
- более тяжелыеспециализированные подклассы
(My3DGraph, My3DGraphNode,
My3DGraphEdge)
В теории пока все хорошо, то есть.
Проблема:
Я должен обеспечить (и желательно при компиляциивремя) что нельзя добавить универсальный MyGenericGraphNodes
к специализированному My3DGraph
, так как это сильно зависит от добавленной 3D логики внутри My3DGraphNode
.(Хотя универсальный MyGenericGraph
просто не заботится.)
Основная проблема проста: я не могу переопределить эти методы из MyGenericGraph:
- (void)addNode:(MyGenericGraphNode *)aNode;
- (void)removeNode:(MyGenericGraphNode *)aNode;
с этими методами в моемподкласс My3DGraph:
- (void)addNode:(My3DGraphNode *)aNode;
- (void)removeNode:(My3DGraphNode *)aNode;
До сих пор я придумал три возможных решения, но прежде чем перейти к любому из них, я хотел бы услышать некоторые мнения о них.(и надеюсь избавить меня от некоторых непредвиденных неприятностей на моем пути)
Мне интересно, есть ли другое и превосходное решение или шаблон дизайна , к которому я скучаю? Или если нет: какое из моих решений вы бы выбрали?
Мне бы очень хотелось услышать ваше мнение по этому поводу.
Возможное решение 1
- Добавление абстрактного класса
MyAbstractGraph
, который в основном будет идентичен родовым частям моей текущей реализации из MyGenericGraph
(см. Ниже), но в будет отсутствовать какой-либо узел-добавление / методы удаления .MyGenericGraph
и My3DGraph
будут просто подклассами MyAbstractGraph
.И хотя MyGenericGraph
будет реализовывать только недостающие методы добавления / удаления узлов, My3DGraph
будет еще больше реализовывать все функциональные возможности трехмерного пространства.Оба требуют своих соответствующих типов классов узлов.(то же самое для MyGenericGraphNode
и MyGenericGraphEdge
и их трехмерных аналогов)
Проблемы с этим решением: Это добавит значительную степень сложности кв противном случае довольно простая проблема .
Кроме того, поскольку My3DGraph
должен иметь возможность иметь дело с My3DGraphNodes
И MyGenericGraphNodes
, я должен был бы реализовать метод MyGenericGraph
как:
- (void)addNode:(MyAbstractGraphNode *)aNode;`
но метод My3DGraph
также:
- (void)addNode:(My3DGraphNode *)aNode;
, так как в противном случае мой общий граф не будет принимать 3d-узлы.Это может привести к ненужному раскрытию абстрактного класса.
Возможное решение 2
Истинные и простые подклассы + перемещение MyGenericGraphNode/My3DGraphNode
выделения прямо в MyGenericGraph/My3DGraph
, чтобы получить что-то вроде: - (MyGenericGraphNode *)newNode;
, который выделит и вернет узел правильного типа и сразу добавит его к графику .Тогда можно было бы полностью избавиться от - (void)addNode:(MyGenericGraphNode *)aNode;
, не оставив шансов добавить узлы, кроме как внутри самого графа (следовательно, гарантируя правильное членство в классе).
Проблемы с этим решением: Хотя это не добавит каких-либо примечательных сложностей к классам, с другой стороны, в принципе, я снова попаду в то же затруднительное положение, как только я, скажем, захотел добавить функциональность вmy My3DGraph
для перемещения узла из одного графика в другой.И imho класс должен иметь возможность иметь дело с объектом независимо от того, кто его создал и почему.
Возможное решение 3
Истинные и простые подклассы + добавление специализированного метода для трехмерных узлов иотключение универсального метода , например, так:
- (void)addNode:(MyGenericGraphNode *)aNode {
[self doesNotRecognizeSelector:_cmd];
}
- (void)add3DNode:(My3DGraphNode *)aNode {
//bla
}
Проблемы с этим решением: Универсальный метод [a3DGraph addNode:aNode]
все равно будет отображаться в автозаполнении Xcode, при компиляции он будет проходить без вывода сообщений., но неожиданно выкидываете исключение при запуске.Предусмотрены частые головные боли.
Возможное решение 4
Истинные и простые подклассы для моего графа и только общие классы для узла и ребра, но с дополнительным указателем ivar My3DUnit *dimensionalUnit:
в классе узла (по умолчанию nil для MyGraph), который реализует всю логику и свойства и обеспечивает3D-функциональность для класса узла. My3DUnit
может быть просто создана без вывода сообщений (например, с позицией (0,0,0)) и присоединена к общим узлам в случае, если они добавляются в трехмерный график и, следовательно, становятся совместимыми.И наоборот, если к общему графу добавляется узел с DL3DUnit
, он просто сохраняет его присоединенным и добавляет узел.
Файлы заголовков
Вот (сокращенные) заголовки моих классов:
@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
По сути, мой график реализован прямо сейчас.Очевидно, что это довольно много, но вы должны понять.
(Как вы, возможно, заметили, MyGenericGraphEdge в настоящее время не реализует функциональность 3D-пространства, но в будущем это может произойти, например, вычисление его центра.точка, например, следовательно, я включил это здесь.)
[Редактировать: Добавлено решение 4, вдохновленное ughoavgfhw;исправлена ошибка в решении 1, извините за это: (]