Что такое хороший шаблон проектирования в C # для классов, которые должны ссылаться на другие классы? - PullRequest
7 голосов
/ 30 октября 2008

Я работаю над проблемой бизнеса в C # .NET. У меня есть два класса, названных C и W, которые будут созданы независимо в разное время.

Объект класса C должен содержать ссылки на 0 ... n объектов класса W, т.е. объект C может содержать до n объектов W.

Каждый объект W должен содержать ссылку ровно на 1 объект класса C, то есть объект W содержится в одном объекте C.

Объект класса C обычно создается первым. Позже его содержимое W обнаруживается и создается. На этом более позднем этапе мне нужно сопоставить объекты C и W друг с другом.

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

Я думал о чем-то простом, как:

class C
{
   public List<W> contentsW;

}

class W
{
  public C containerC;

}

На данный момент это будет работать, но я могу предвидеть, что придется написать изрядное количество кода, чтобы отслеживать все ссылки и их достоверность. Я хотел бы реализовать код в будущем, чтобы делать поверхностные обновления только контейнера и глубокие обновления всех ссылочных классов. Есть ли другие подходы и в чем их преимущества?

Изменить на 11/3: Спасибо всем за хорошие ответы и хорошее обсуждение. Я, наконец, выбрал ответ Jop, потому что это было ближе всего к тому, что я хотел сделать, но другие ответы также помогли. Еще раз спасибо!

Ответы [ 6 ]

6 голосов
/ 30 октября 2008

Если у вас есть книга Рефакторинга Мартина Фаулера, просто следуйте рефакторингу «Измените однонаправленную ассоциацию на Двунаправленный».

Если у вас его нет, вот как будут выглядеть ваши классы после рефакторинга:

class C
{
  // Don't to expose this publicly so that 
  // no one can get behind your back and change 
  // anything
  private List<W> contentsW; 

  public void Add(W theW)
  {
    theW.Container = this;
  }

  public void Remove(W theW)
  {
    theW.Container = null;
  }

  #region Only to be used by W
  internal void RemoveW(W theW)
  {
    // do nothing if C does not contain W
    if (!contentsW.Contains(theW))
       return; // or throw an exception if you consider this illegal
    contentsW.Remove(theW);
  }

  internal void AddW(W theW)
  {
    if (!contentW.Contains(theW))
      contentsW.Add(theW);
  }
  #endregion
}

class W
{
  private C containerC;

  public Container Container
  {
    get { return containerC; }
    set 
    { 
      if (containerC != null)
        containerC.RemoveW(this);
      containerC = value; 
      if (containerC != null)
        containerC.AddW(this);
    }
  }
}

Обратите внимание, что я сделал List<W> приватным. Предоставьте список Ws через перечислитель, а не предоставьте список напрямую.

например. публичный список GetWs () {return this.ContentW.ToList (); }

Код выше обрабатывает передачу права собственности должным образом. Скажем, у вас есть два экземпляра C - C1 и C2 - и экземпляры W - W1 и W2.

W1.Container = C1;
W2.Container = C2;

В приведенном выше коде C1 содержит W1, а C2 содержит W2. Если вы переназначаете W2 на C1

W2.Container = C1;

Тогда у С2 будет ноль предметов, а у С1 будет два предмета - W1 и W2. Вы можете иметь плавающий W

W2.Container = null;

В этом случае W2 будет удален из списка C1, и у него не будет контейнера. Вы также можете использовать методы Add и Remove из C для манипулирования контейнерами W - поэтому C1.Add (W2) автоматически удалит W2 из своего исходного контейнера и добавит его в новый.

3 голосов
/ 30 октября 2008

Обычно я делаю что-то вроде этого:

class C
{
   private List<W> _contents = new List<W>();
   public IEnumerable<W> Contents
   {
      get { return _contents; }
   }

   public void Add(W item)
   {
      item.C = this;
      _contents.Add(item);
   }
}

Таким образом, ваше свойство Contents доступно только для чтения, и вы добавляете элементы только с помощью метода агрегата.

2 голосов
/ 30 октября 2008

Хммм, похоже, вы почти получили его, с одним незначительным затруднением - вы должны иметь возможность контролировать добавление в список в C.

например.,

class C
{
    private List<W> _contentsW;

    public List<W> Contents 
    {
        get { return _contentsw; }
    }

    public void AddToContents(W content);
    {
        content.Container = this;
        _contentsW.Add(content);
    }
}

Для проверки вам просто нужно перебрать свой список, я думаю:

foreach (var w in _contentsW)
{
    if (w.Container != this)
    {
        w.Container = this;
    }
}

Не уверен, что это то, что тебе нужно.

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

1 голос
/ 30 октября 2008

Расширение на Джонс Ответ ....

Вам могут понадобиться слабые ссылки, если W не должен поддерживать C в живых.

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

public void AddToContents(W content);
{  
   if(content.Container!=null) content.Container.RemoveFromContents(content);
    content.Container = this;
    _contentsW.Add(content);
}
0 голосов
/ 30 октября 2008

Это шаблон, который я использую.

public class Parent {
    public string Name { get; set; }
    public IList<Child> Children { get { return ChildrenBidi; } set { ChildrenBidi.Set(value); } }
    private BidiChildList<Child, Parent> ChildrenBidi { get {
        return BidiChildList.Create(this, p => p._Children, c => c._Parent, (c, p) => c._Parent = p);
    } }
    internal IList<Child> _Children = new List<Child>();
}

public class Child {
    public string Name { get; set; }
    public Parent Parent { get { return ParentBidi.Get(); } set { ParentBidi.Set(value); } }
    private BidiParent<Child, Parent> ParentBidi { get {
        return BidiParent.Create(this, p => p._Children, () => _Parent, p => _Parent = p);
    } }
    internal Parent _Parent = null;
}

Очевидно, у меня есть классы BidiParent<C, P> и BidiChildList<C, P>, последний из которых реализует IList<C> и т. Д. Закулисные обновления выполняются через внутренние поля, тогда как обновления из кода, использующего эту модель домена сделано через публичные свойства.

0 голосов
/ 30 октября 2008

Одним из вариантов для этого будет реализация интерфейсов IContainer и IComponent , найденных в System.ComponentModel. C будет контейнером, а W - компонентом. Класс ComponentCollection будет служить хранилищем для ваших экземпляров W, а IComponent.Site предоставит обратную ссылку на C.

...