Создание подклассов набора взаимозависимых классов в Objective-C и обеспечение безопасности типов - PullRequest
4 голосов
/ 23 февраля 2011

Я реализовал базовый класс графов (не как в «построении графиков», а как в «сети»!), Который будет использоваться для базовых теоретических задач по графам . (см. Обобщенные фрагменты заголовочного файла ниже)

В дополнение к общей функциональности он также реализует функциональность для позиционирования узла в трехмерном пространстве . И эту расширенную 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, извините за это: (]

Ответы [ 2 ]

1 голос
/ 24 февраля 2011

Раствор, быстрый и грязный:

После повторного анализа структуры моего класса и обнаружения нескольких ранее непредвиденных последствий для потенциальных подводных камней в будущем развитии для моего запланированного семейства классов графов я пришел к выводу в основном придерживайтесь предложенного мной решения 4, но сопровождайте его серьезной реструктуризацией (см. прилагаемую упрощенную диаграмму ER). Мой план состоит в том, чтобы вместо тяжелых многоцелевых über-классов иметь несколько одноцелевых компонентов , которые (если они хорошо сконструированы) могут быть объединены в различные виды специального назначения ( и относительно легкий) наборы инструментов .

Потенциальные ловушки простого наследования:

Если я реализую наборы пространственных объектов в подклассе MyGenericGraph, то это делает для меня практически невозможным создание более конкретных подклассов графа (для специализированных деревьев, например), которые могут быть либо легкими, либо универсальный (например, MyGenericGraph) или размерный (например, My3DGraph). Для класса MyGenericTree (например, для анализа дерева) я должен был бы создать подкласс MyGenericGraph. Однако для My3DTree (например, для отображения дерева) я должен был бы создать подкласс My3DGraph. My3DTree может поэтому не наследовать никакой логики от MyGenericTree. Я бы реализовал пространственные элементы с избыточностью. Плохой. Довольно плохо.

Предлагаемая архитектура структуры класса:

  • Полностью избавьтесь от любых «мерных» классов . Сохраняйте классы строго базовыми структурами данных только с базовой и необходимой логикой.

  • Представьте класс MyVertex, который обеспечивает размерные атрибуты и методы для узлов, если это необходимо (добавив MyVertex *vertex ivar к MyGraphNode (по умолчанию nil)). Это также значительно упрощает повторное использование в MyVertexCloud, простом контейнере облака точек, который должен пригодиться для улучшения моего алгоритма принудительного размещения.

  • Передать любую логику, которая не является строго необходимой , для структуры данных графиков специальным вспомогательным классам . Таким образом, MyGraphNodeClusterRelaxer будет отвечать за любую логику, характерную для визуальной разметки графов.

  • Подклассы MyGraph будут быстрыми и легкими благодаря моей одноцепочечной наследственности цепочка и модульность .

  • Использование внешнего MyGraphNodeClusterRelaxer также позволит мне ослабить подмножество узлов графа , а не только весь граф, как это сделал бы My3DGraph.

  • MyGraphNodeCluster будет не более чем оболочкой для набора узлов (все одного и того же графа). Его подклассы могут быть более конкретными в критериях и алгоритмах членства в кластере.

  • Получение MyGraphNodeCluster для всего графа будет при easy при вызове (MyGraphNodeCluster *)[myGraph nodeCluster]; и оттуда вы получите MyVertexCloud через (MyVertexCloud *)[myNodeCluster vertexCloud];. Обратное (для последнего) невозможно по очевидным причинам.

(упрощенная) реляционная модель сущностей:

enter image description here

0 голосов
/ 23 февраля 2011

Лично я бы не использовал ни один из них. Я собирался предложить что-то похожее на ваше второе решение в качестве альтернативы, но я думаю, что это лучший способ:

Объявите addNode как - (void)addNode:(MyGenericGraphNode *)node. Затем, в реализации, убедитесь, что это правильный класс. Это еще лучший выбор, так как вы упомянули в разделе проблемы с решением 1 , что вы хотели, чтобы трехмерный граф также обрабатывал общие узлы. Я не знаю, как вы хотите их обработать, но вы могли обнаружить, что новый узел не был 3D, и создать новый трехмерный узел из него, например, просто установив координату z на 0 для всего.

Пример:

//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
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...