Дизайн двойной связи Родитель - Ребенок - PullRequest
1 голос
/ 20 июля 2010

Я определил C # -класс, который должен быть элементами ориентированного графа (в основном это дерево, но у элемента может быть несколько родителей - я не знаю, существует ли для этого специальное имя).

Теперь каждый элемент должен иметь всех своих детей и всех своих родителей.Он отображает эти списки как IEnumarable

public interface IMyClass
{
  public IEnumerable<MyClass> Children { get; }
  public IEnumerable<MyClass> Parents { get; }
}

public class MyClass : IMyClass
{
  private List<MyClass> _parents;
  private List<MyClass> _children;

  public IEnumerable<MyClass> Children
  {
    get { foreach (var child in _children) yield return child; }
  }

  public IEnumerable<MyClass> Parents
  {
    get { foreach (var parent in _parents) yield return parent; }
  }

При добавлении или удалении дочернего элемента к данному элементу я хочу убедиться, что данный элемент добавлен или удален в родительский список дочернего элемента также.

Первой идеей было показать только метод AddChild (MyClass theChild).Проблема в том, что я не могу добавить родителя к объекту theChild, потому что он не предоставляет метод AddParent (parent), и я не могу вызвать частные методы объекта theChild.

Поэтому я попыталсяпойти с выставлением метода AddParent (MyClass theParent) тоже.Чтобы по-прежнему удостовериться, что обе ссылки на объекты установлены, мой первый снимок вызывал AddParent / AddChild в другой функции, подобной этой:

public void AddChild(IMyClass theChild)
{
  _children.Add(theChild);
  theChild.AddParent(this);
}

public void AddParent(IMyClass theParent)
{
  _parent.Add(theParent);
  theParent.AddChild(this);
}

, но очевидно, что это смертельный цикл.

Что еще хуже, я хочу допустить, чтобы элемент мог быть дочерним по отношению к другому элементу несколько раз (это еще не требование, но я хочу убедиться, что КОГДА это требование выполняется, мой код не должен бытьтронут.)

Существуют ли какие-либо алгоритмы / стандартные подходы, которые позволяют мне убедиться, что при добавлении дочернего элемента к родителю всегда установлены обе ссылки на объекты?

Заранее спасибо,
Фрэнк

Эдит: Добавлены интерфейсы для исправления примера.

Ответы [ 4 ]

2 голосов
/ 20 июля 2010

Вы можете сделать это:

public void AddChild(MyClass theChild)
{
    _children.Add(theChild);
    theChild._parent.Add(this);
}

public void AddParent(MyClass theParent)
{
    _parent.Add(theParent);
    theParent._children.Add(this);
}

С этим не должно быть проблем; просто то, что вы ссылаетесь на другой экземпляр класса, для которого вы пишете метод, не означает, что вы не можете получить доступ к его членам (даже если они закрытые).


ОК с исправленным кодом, вы можете добавить двух новых членов в ваш интерфейс: NotifyChildAdded и NotifyParentAdded и реализовать так:

public void AddChild(MyClass theChild)
{
    _children.Add(theChild);
    theChild.NotifyParentAdded(this);
}

public void AddParent(MyClass theParent)
{
    _parent.Add(theParent);
    theParent.NotifyChildAdded(this);
}

public void NotifyChildAdded(MyClass theChild)
{
    _children.Add(theChild);
}

public void NotifyParentAdded(MyClass theParent)
{
    _parent.Add(theParent);
}

Надеюсь, это поможет!

0 голосов
/ 20 июля 2010

Другой возможный дизайн - хранить отношения между узлами в объекте Graph, а не встраивать эту информацию в объекты Node.Если вы все еще хотите работать с классами Node, вы можете передать интерфейс для Graph на Nodes, чтобы они могли предоставлять «удобные» функции, такие как GetParents (), GetChildren (), AddParent (), AddChild (), которые вызываютсоответствующие публичные функции Graph.Идея заключается в том, что узлы не должны отвечать за управление операциями с графами;это должно быть ответственностью Графа.

Одна простая реализация - это матрица смежности.Это логический массив NxN, где N - это число узлов, а содержимое A [i, j] указывает, существует ли связь от узла i к узлу j.Это делает обновление родительских / дочерних отношений O (1) (просто устанавливает / сбрасывает флаг) и производит перечисление родителей и дочерних элементов для данного узла O (N).Недостатком является то, что вы используете O (N ^ 2) пробел.Это хороший подход, если вы хотите часто изменять график.Он также обобщается на взвешенный график, если вы храните веса вместо флагов.

Вы можете использовать подход типа «список списков», если хотите сэкономить место.Это действительно полезно только в том случае, если количество ссылок намного меньше, чем количество узлов (т. Е. Граф редко связан), а количество узлов велико.

0 голосов
/ 20 июля 2010

Один из вариантов - выставить свойства списка как внутренние и иметь открытые методы добавления, которые будут вызывать доступ к внутренним свойствам другого класса.

Другой вариант - иметь тупые данные и отдельный уровень бизнес-логикиэто то, что вызывает внешний код.

0 голосов
/ 20 июля 2010

... но я хочу убедиться, что КОГДА это требование выполняется, мой код не нужно трогать.
Это явное нарушение принципа ЯГНИ ( оно вам не понадобится ). Может быть, вы должны просто начать кодировать его так, как вам нужно прямо сейчас (это означает использование Contains() ^^). И позаботьтесь об изменениях в ваших требованиях, когда они придут ...

...