проблема
Рассмотрим реализацию графа, SampleGraph<N>
.
Рассмотрим реализацию узлов графа Node extends N
, корректно переопределяющую hashCode
и equals
для отражения логического равенства между двумя узлами.
Теперь, допустим, мы хотим добавить какое-либо свойство p к узлу. Такое свойство привязано к логическим экземплярам узла, то есть для Node n1, n2
, n1.equals(n2)
подразумевает p (n1
) = p (n2
)
Если я просто добавлю свойство как поле класса Node
, это произойдет со мной:
- Я определяю
Node n1, n2
таким, что n1.equals(n2)
, но n1 != n2
- Я добавляю
n1
и n2
к графику: n1
при вставке логического узла и n2
при обращении к узлу при вставке ребер. На графике хранятся оба экземпляра.
- Позже я извлекаю узел из графика (возвращается
n1
) и устанавливаю для него свойство p в какое-то значение. Позже я пересекаю все ребра графа и извлекаю узел у одного из них (возвращается n2
). Свойство p не установлено, что вызывает логическую ошибку в моей модели.
Подводя итог, текущее поведение :
graph.addNode(n1) // n1 is added
graph.addEdge(n2,nOther) // graph stores n2
graph.queryForNode({query}) // n1 is returned
graph.queryForEdge({query}).sourceNode() // n2 is returned
Вопрос
Все следующие утверждения кажутся мне разумными. Никто из них не убеждает меня полностью в этом, поэтому я ищу рекомендации по передовой практике, основанные на канонах разработки программного обеспечения.
S1 - Реализация графика плохая. После добавления узла граф всегда должен внутренне проверять, запомнен ли экземпляр того же узла (equals
оценивается как true). Если это так, такой экземпляр всегда должен быть единственной ссылкой, используемой графом.
graph.addNode(n1) // n1 is added
graph.addEdge(n2,nOther) // graph internally checks that n2.equals(n1), doesn't store n2
graph.queryForNode({query}) // n1 is returned
graph.queryForEdge({query}).sourceNode() // n1 is returned
S2 - Предполагать, что график ведет себя так, как в S1, является ошибкой. Программист должен позаботиться о том, чтобы в граф всегда передавался один и тот же экземпляр узла.
graph.addNode(n1) // n1 is added
graph.addEdge(n1,nOther) // the programmer uses n1 every time he refers to the node
graph.queryForNode({query}) // n1 is returned
graph.queryForEdge({query}).sourceNode() // n1 is returned
S3 - Свойство реализовано неправильно. Это должна быть информация, которая является внешней по отношению к классу Node
. Коллекция, такая как HashMap<N, Property>
, будет работать нормально, обрабатывая разные экземпляры как один и тот же объект на основе hashCode
.
HashMap<N, Property> properties;
graph.addNode(n1) // n1 is added
graph.addEdge(n2,nOther) // graph stores n2
graph.queryForNode({query}) // n1 is returned
graph.queryForEdge({query}).sourceNode() // n2 is returned
// get the property. Difference in instances does not matter
properties.get(n1)
properties.get(n2) //same property is returned
S4 - То же, что S3, но мы могли бы скрыть реализацию внутри Node
, таким образом:
class Node {
private static HashMap<N, Property> properties;
public Property getProperty() {
return properties.get(this);
}
}
Редактировать : добавлены фрагменты кода для текущего поведения и предварительные решения после Стивен C answer . Для пояснения, весь пример взят из использования реальной структуры данных графа из проекта Java с открытым исходным кодом.