Composite: ссылки на родителя
От: Пельмешко Россия blog
Дата: 11.05.11 17:38
Оценка:
Здравствуйте, уважаемые кывт'еры!

Занимаясь разработкой я сталкивался с необходимостью иметь коллекции некоторых Элементов, которые вынуждены знать о коллекции, в которую они добавлены (Родителе), а так же встречал подобные ситуации в различных фреймворках. Часто с подобной ситуацией сталкивался при описании различных моделей, иерархии классов которых построенны согласно шаблону проектирования Composite. Например, некоторому элементу могло требоваться знать в каком "контексте" он находится — приходилось спускаться по дереву вниз, к Родителям.

Необходимость иметь в элементе ссылку на Родителя создаёт проблемы:


При этом в .NET я не встречал никаких средств обобщения, каких-нибудь специальных коллекций, осуществляющих слежение за инвариантом ссылок на Родителей.
Собственно я прошу как-то формализовать мой взгляд на проблему ссылок на Родителей, может кто подскажет годные статьи по теме, какие-нибудь шаблоны проектирования или альтернативные подходы, что-нибудь ещё...
Re: Composite: ссылки на родителя
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.05.11 04:33
Оценка: 9 (1) +1
Здравствуйте, Пельмешко, Вы писали:

П>Здравствуйте, уважаемые кывт'еры!


П>Занимаясь разработкой я сталкивался с необходимостью иметь коллекции некоторых Элементов, которые вынуждены знать о коллекции, в которую они добавлены (Родителе), а так же встречал подобные ситуации в различных фреймворках. Часто с подобной ситуацией сталкивался при описании различных моделей, иерархии классов которых построенны согласно шаблону проектирования Composite. Например, некоторому элементу могло требоваться знать в каком "контексте" он находится — приходилось спускаться по дереву вниз, к Родителям.

В этом и заключается проблема композита. Объекты, которые являются узлами дерева, обрабатываются как самостоятельные объекты, которые как-бы не в дереве. Их интерфейсы (в паттерне композит обычно так) содержат методы, заточенные для работы с одним объектом, без передачи контекстов, вроде цепочки родителей.
Вот что интересно, если объекты такого дерева обрабатываются всем скопом, то внеся в сигнатуры методов обработки цепочки родителей, можем избавиться от хранения родителей и проблем, с этим связанных.
В зависимости от обстоятельств, можно вместо композита обходиться декоратором, который хранит в себе элемент дерева + цепочку родителей, либо корень дерева + XPath, либо как-то еще.

П>Необходимость иметь в элементе ссылку на Родителя создаёт проблемы:


П>
в WinForms два пути добавления (через установку Parent и через добавление в коллекцию). Потому можно смело скрыть один из них в своем дереве.

П>При этом в .NET я не встречал никаких средств обобщения, каких-нибудь специальных коллекций, осуществляющих слежение за инвариантом ссылок на Родителей.

Тут каждый городит свой огород, например http://stackoverflow.com/questions/3845966/how-to-make-an-intrusive-tree-class-in-c-use-generics

П>Собственно я прошу как-то формализовать мой взгляд на проблему ссылок на Родителей, может кто подскажет годные статьи по теме, какие-нибудь шаблоны проектирования или альтернативные подходы, что-нибудь ещё...

Нет, ничего не подскажу. Сам я пришел к тому, что лучше не прятать дерево за одним элементом, в большинстве случаев последнее время обхожусь иммутабельными деревьями без родителей.
Разве что хранение контекста (цепочки родителей или пути в дереве) в декораторе.
Re[2]: Composite: ссылки на родителя
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 13.05.11 12:12
Оценка:
Здравствуйте, samius, Вы писали:

S>Вот что интересно, если объекты такого дерева обрабатываются всем скопом, то внеся в сигнатуры методов обработки цепочки родителей, можем избавиться от хранения родителей и проблем, с этим связанных.


А как тогда найти рутовый объект ?

S>В зависимости от обстоятельств, можно вместо композита обходиться декоратором, который хранит в себе элемент дерева + цепочку родителей, либо корень дерева + XPath, либо как-то еще.


А по поподробнее можно ?
Re: Composite: ссылки на родителя
От: Sinix  
Дата: 13.05.11 12:43
Оценка: 12 (1)
Здравствуйте, Пельмешко, Вы писали:

П>Занимаясь разработкой я сталкивался с необходимостью иметь коллекции некоторых Элементов, которые вынуждены знать о коллекции, в которую они добавлены (Родителе), а так же встречал подобные ситуации в различных фреймворках.


  это?
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace UniversIS.Collections.ObjectModel
{
  // Обработать напильником. 
  // Ещё можно скопипастить аналогичный класс для наследника от KeyedCollectionBase<TKey, TItem>
  [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix",
    Justification = CodeAnalysisConstants.CA1710NamedToIllustrateUsage)]
  public abstract class OwnedCollectionBase<TOwner, TItem>: DisposableCollection<TItem>
    where TOwner: class
    where TItem: class
  {
    private static readonly string newItemHasOwnerMessage = CodeResources.MappedCollectionBase_NewItemHasOwner;

    private TOwner owner;

    protected OwnedCollectionBase(TOwner owner)
    {
      Code.NotNull(owner, "owner");
      this.owner = owner;
    }

    // TODO: common logic to helper???
    #region Core logic (same as KeyedOwnedCollectionBase)
    protected TOwner Owner
    {
      get
      {
        return owner;
      }
      set
      {
        Code.NotNull(value, "value");
        foreach (TItem item in this)
        {
          SetOwner(item, value);
        }
        owner = value;
      }
    }

    protected override void InsertItem(int index, TItem item)
    {
      ValidateNotDisposed();
      Code.NotNull(item, "item");
      SetOwnerOnNewItem(item);
      base.InsertItem(index, item);
    }

    protected override void SetItem(int index, TItem item)
    {
      ValidateNotDisposed();
      Code.NotNull(item, "item");
      SetOwnerOnNewItem(item);
      ClearOwner(this[index]);
      base.SetItem(index, item);
    }

    protected override void RemoveItem(int index)
    {
      ValidateNotDisposed();
      ClearOwner(this[index]);
      base.RemoveItem(index);
    }

    protected override void ClearItems()
    {
      foreach (TItem item in this)
      {
        ClearOwner(item);
      }
      base.ClearItems();
    }

    private void SetOwnerOnNewItem(TItem item)
    {
      Code.AssertState(!HasOwner(item), newItemHasOwnerMessage);
      SetOwner(item, owner);
    }

    protected abstract bool HasOwner(TItem item);
    protected abstract void ClearOwner(TItem item);
    protected abstract void SetOwner(TItem item, TOwner owner);
    #endregion

    [ContractArgumentValidator, DebuggerHidden]
    protected void ValidateNotDisposed()
    {
      Code.NotDisposed(owner != null, GetType().FullName);
    }

    protected override void Dispose(bool disposing)
    {
      base.Dispose(disposing);
      if (disposing)
      {
        owner = null;
      }
    }
  }

  public class DisposableCollection<T>: Collection<T>, IDisposable
  {
    #region IDisposable Members
    protected virtual void Dispose(bool disposing)
    {
      if (disposing)
      {
        foreach (var item in this.OfType<IDisposable>())
        {
          item.Dispose();
        }
        Clear();
      }
    }

    public void Dispose()
    {
      Dispose(true);
      GC.SuppressFinalize(this);
    }
    #endregion
  }
}
Re[3]: Composite: ссылки на родителя
От: samius Япония http://sams-tricks.blogspot.com
Дата: 13.05.11 12:46
Оценка:
Здравствуйте, Ikemefula, Вы писали:

I>Здравствуйте, samius, Вы писали:


S>>Вот что интересно, если объекты такого дерева обрабатываются всем скопом, то внеся в сигнатуры методов обработки цепочки родителей, можем избавиться от хранения родителей и проблем, с этим связанных.


I>А как тогда найти рутовый объект ?

Он известен.

S>>В зависимости от обстоятельств, можно вместо композита обходиться декоратором, который хранит в себе элемент дерева + цепочку родителей, либо корень дерева + XPath, либо как-то еще.


I>А по поподробнее можно ?


class Node 
{
    IEnumerable<Node> Children { get; } 
    string Name { get; }
    
    // любая операция по работе с родителями требует наличие этих родителей, 
    // но они не хранятся в узле,
    // значит надо передавать. Так же любой поиск чего-либо в дереве должен возвращать цепочку парентов для
    // узла.
    Tuple<Node, IEnumerable<Node>> GetRoot(IEnumerable<Node> parents)
    {
        return parents.Any() 
            ? parents.First().GetRoot(parents.Skip(1))
            : Tuple.Create(this, parents);
    }
}

interface IFoo
{
    IFoo GetRoot();
    string Name { get; }
}

class NodeAdapter : IFoo
{
    Node Node { get; }
    IEnumerable<Node> Parents { get; }

    public IFoo GetRoot()
    {
        var r = Node.GetRoot(Parents);
        return new NodeAdapter(r.Item1, r.Item2);
    }
    public string Name { get { return Node.Name; } }
}
Re[4]: Composite: ссылки на родителя
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 13.05.11 14:23
Оценка:
Здравствуйте, samius, Вы писали:

I>>А как тогда найти рутовый объект ?

S>Он известен.

Кому, тебе, Васе Пупкину ? Или ты предлагаешь протащить во все операции, свойства и вообще все внутренности параметр этого рута ?

S>>>В зависимости от обстоятельств, можно вместо композита обходиться декоратором, который хранит в себе элемент дерева + цепочку родителей, либо корень дерева + XPath, либо как-то еще.


I>>А по поподробнее можно ?


S>
S>    Tuple<Node, IEnumerable<Node>> GetRoot(IEnumerable<Node> parents)
S>    {
S>        return parents.Any() 
S>            ? parents.First().GetRoot(parents.Skip(1))
S>            : Tuple.Create(this, parents);
S>    }
S>}
S>


То есть, ты предлашаешь протащить рута в виде параметра везде где он может понадобиться ?

Все операции придется гонять через рута и заниматься написанием водопроводного кода. Такое точно не надо, пользуйся сам.
Re[5]: Composite: ссылки на родителя
От: samius Япония http://sams-tricks.blogspot.com
Дата: 13.05.11 14:48
Оценка: +1
Здравствуйте, Ikemefula, Вы писали:

I>Здравствуйте, samius, Вы писали:


I>>>А как тогда найти рутовый объект ?

S>>Он известен.

I>Кому, тебе, Васе Пупкину ? Или ты предлагаешь протащить во все операции, свойства и вообще все внутренности параметр этого рута ?


В таком духе продолжай сам с собой
Re[6]: Composite: ссылки на родителя
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 13.05.11 15:04
Оценка: -1
Здравствуйте, samius, Вы писали:

I>>>>А как тогда найти рутовый объект ?

S>>>Он известен.

I>>Кому, тебе, Васе Пупкину ? Или ты предлагаешь протащить во все операции, свойства и вообще все внутренности параметр этого рута ?


S>В таком духе продолжай сам с собой


Ты сам начал
Re: Composite: ссылки на родителя
От: _FRED_ Черногория
Дата: 17.05.11 17:47
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>При этом в .NET я не встречал никаких средств обобщения, каких-нибудь специальных коллекций, осуществляющих слежение за инвариантом ссылок на Родителей.


Мне достаточно стандартного Collection<>.
  Скрытый текст
    public class OwnedCollection : Collection<TOwned>
    {
        public OwnedCollection (TOwner owner)
        {
            if (owner == null)
            {
                throw new ArgumentNullException("owner");
            }
                        
            Owner = owner;
        }

        public TOwner Owner { get; private set; }

        protected virtual void AttachItem(TOwned item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            else if(item.Owner != null)
            {
                throw new ArgumentException("item.Owner != null", "item");
            }

            item.Owner = this; // || item.Owner = Owner;
        }

        protected virtual void DetachItem(TOwned item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            item.Owner = null;
        }

        protected override void InsertItem(int index, T item)
        {
            AttachItem(item);
            base.InsertItem(index, item);
        }

        protected override void SetItem(int index, TOwned item)
        {
            AttachItem(item);
            DetachItem(this[index]);
            base.SetItem(index, item);
        }

        protected override void RemoveItem(int index)
        {
            DetachItem(this[index]);
            base.RemoveItem(index);
        }

        protected override void ClearItems()
        {
            foreach (var item in Items)
            {
                DetachItem(item);
            }

            base.ClearItems();
        }
    }


П>Собственно я прошу как-то формализовать мой взгляд на проблему ссылок на Родителей, может кто подскажет годные статьи по теме, какие-нибудь шаблоны проектирования или альтернативные подходы, что-нибудь ещё...


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

О том, как это влияет на композит подскажу только, что делать сложный объект как коллекцию других объектов кажется неверным: удобнее делать такой объект обычным объектом (не коллекцией) в котором будет свойство типа коллекции. Но к вопросам инварианта это вроде как отношения не имеет.
Help will always be given at Hogwarts to those who ask for it.
Re: Composite: ссылки на родителя
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 17.05.11 20:30
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>При этом в .NET я не встречал никаких средств обобщения, каких-нибудь специальных коллекций, осуществляющих слежение за инвариантом ссылок на Родителей.

П>Собственно я прошу как-то формализовать мой взгляд на проблему ссылок на Родителей, может кто подскажет годные статьи по теме, какие-нибудь шаблоны проектирования или альтернативные подходы, что-нибудь ещё...

http://rsdn.ru/forum/design/4009300.1.aspx
Автор: Ikemefula
Дата: 22.10.10
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.