Занимаясь разработкой я сталкивался с необходимостью иметь коллекции некоторых Элементов, которые вынуждены знать о коллекции, в которую они добавлены (Родителе), а так же встречал подобные ситуации в различных фреймворках. Часто с подобной ситуацией сталкивался при описании различных моделей, иерархии классов которых построенны согласно шаблону проектирования Composite. Например, некоторому элементу могло требоваться знать в каком "контексте" он находится — приходилось спускаться по дереву вниз, к Родителям.
Необходимость иметь в элементе ссылку на Родителя создаёт проблемы:
Необходимо отслеживать ситуации, когда один Элемент добавляется в несколько Родителей.
И политика тут может быть разная, я встречал два подхода: запрещать подобные ситуации, просто бросая исключение при добавлении к воллекцию Элемента, уже имеющего Родителя, или делать глубокую копию Элемента и добавлять в другого Родителя её (подобный подход в .NET применяется в классах Linq to XML). Второй подход имеет узкое применение, вынуждает описывать клонирование (подвержено ошибкам и потенциально ударяет по производительности) и не нравится лично мне тот факт, что добавляя Элемент в Родителя нельзя быть уверенным, что в Родителя добавится Элемент, ссылочно равный тому, который изначально добавляли.
Необходимо управлять видимостью свойства/метода, задающего элементу родителя.
В случае паттерна composite, можно просто объявить свойство с public-геттером и protected-сеттером. Однако Родитель не всегда разделяет с Элементом общий базовый класс, тогда приходится использовать более широкую область видимости, типа internal в дотнет. Иногда встречаются свойста родителей вовсе с public-сеттером (Windows.Forms тому пример), которые сами удаляются из бывшего Родителя и добавляются в коллекцию нового. Такой подход лично мне не нравится (слишком много видимых снаружи эффектов для "простого" присвоения свойству), да и вовсе не годится, если порядок дочерних Элементов в коллекции Родителя имеет значение (в Windows.Forms при задании нового Parent'а, контрол просто добавляется в конец коллекции контролов), хоть и может выглядеть удобно.
При этом в .NET я не встречал никаких средств обобщения, каких-нибудь специальных коллекций, осуществляющих слежение за инвариантом ссылок на Родителей.
Собственно я прошу как-то формализовать мой взгляд на проблему ссылок на Родителей, может кто подскажет годные статьи по теме, какие-нибудь шаблоны проектирования или альтернативные подходы, что-нибудь ещё...
Здравствуйте, Пельмешко, Вы писали:
П>Здравствуйте, уважаемые кывт'еры!
П>Занимаясь разработкой я сталкивался с необходимостью иметь коллекции некоторых Элементов, которые вынуждены знать о коллекции, в которую они добавлены (Родителе), а так же встречал подобные ситуации в различных фреймворках. Часто с подобной ситуацией сталкивался при описании различных моделей, иерархии классов которых построенны согласно шаблону проектирования Composite. Например, некоторому элементу могло требоваться знать в каком "контексте" он находится — приходилось спускаться по дереву вниз, к Родителям.
В этом и заключается проблема композита. Объекты, которые являются узлами дерева, обрабатываются как самостоятельные объекты, которые как-бы не в дереве. Их интерфейсы (в паттерне композит обычно так) содержат методы, заточенные для работы с одним объектом, без передачи контекстов, вроде цепочки родителей.
Вот что интересно, если объекты такого дерева обрабатываются всем скопом, то внеся в сигнатуры методов обработки цепочки родителей, можем избавиться от хранения родителей и проблем, с этим связанных.
В зависимости от обстоятельств, можно вместо композита обходиться декоратором, который хранит в себе элемент дерева + цепочку родителей, либо корень дерева + XPath, либо как-то еще.
П>Необходимость иметь в элементе ссылку на Родителя создаёт проблемы:
П>
П>Необходимо отслеживать ситуации, когда один Элемент добавляется в несколько Родителей. П>И политика тут может быть разная, я встречал два подхода: запрещать подобные ситуации, просто бросая исключение при добавлении к воллекцию Элемента, уже имеющего Родителя, или делать глубокую копию Элемента и добавлять в другого Родителя её (подобный подход в .NET применяется в классах Linq to XML). Второй подход имеет узкое применение, вынуждает описывать клонирование (подвержено ошибкам и потенциально ударяет по производительности) и не нравится лично мне тот факт, что добавляя Элемент в Родителя нельзя быть уверенным, что в Родителя добавится Элемент, ссылочно равный тому, который изначально добавляли.
+1
П>Необходимо управлять видимостью свойства/метода, задающего элементу родителя. П>В случае паттерна composite, можно просто объявить свойство с public-геттером и protected-сеттером. Однако Родитель не всегда разделяет с Элементом общий базовый класс, тогда приходится использовать более широкую область видимости, типа internal в дотнет. Иногда встречаются свойста родителей вовсе с public-сеттером (Windows.Forms тому пример), которые сами удаляются из бывшего Родителя и добавляются в коллекцию нового. Такой подход лично мне не нравится (слишком много видимых снаружи эффектов для "простого" присвоения свойству), да и вовсе не годится, если порядок дочерних Элементов в коллекции Родителя имеет значение (в Windows.Forms при задании нового Parent'а, контрол просто добавляется в конец коллекции контролов), хоть и может выглядеть удобно. П>
в WinForms два пути добавления (через установку Parent и через добавление в коллекцию). Потому можно смело скрыть один из них в своем дереве.
П>При этом в .NET я не встречал никаких средств обобщения, каких-нибудь специальных коллекций, осуществляющих слежение за инвариантом ссылок на Родителей.
Тут каждый городит свой огород, например http://stackoverflow.com/questions/3845966/how-to-make-an-intrusive-tree-class-in-c-use-generics
П>Собственно я прошу как-то формализовать мой взгляд на проблему ссылок на Родителей, может кто подскажет годные статьи по теме, какие-нибудь шаблоны проектирования или альтернативные подходы, что-нибудь ещё...
Нет, ничего не подскажу. Сам я пришел к тому, что лучше не прятать дерево за одним элементом, в большинстве случаев последнее время обхожусь иммутабельными деревьями без родителей.
Разве что хранение контекста (цепочки родителей или пути в дереве) в декораторе.
Здравствуйте, samius, Вы писали:
S>Вот что интересно, если объекты такого дерева обрабатываются всем скопом, то внеся в сигнатуры методов обработки цепочки родителей, можем избавиться от хранения родителей и проблем, с этим связанных.
А как тогда найти рутовый объект ?
S>В зависимости от обстоятельств, можно вместо композита обходиться декоратором, который хранит в себе элемент дерева + цепочку родителей, либо корень дерева + XPath, либо как-то еще.
Здравствуйте, Пельмешко, Вы писали: П>Занимаясь разработкой я сталкивался с необходимостью иметь коллекции некоторых Элементов, которые вынуждены знать о коллекции, в которую они добавлены (Родителе), а так же встречал подобные ситуации в различных фреймворках.
это?
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
}
}
Здравствуйте, 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; } }
}
Здравствуйте, samius, Вы писали:
I>>А как тогда найти рутовый объект ? S>Он известен.
Кому, тебе, Васе Пупкину ? Или ты предлагаешь протащить во все операции, свойства и вообще все внутренности параметр этого рута ?
S>>>В зависимости от обстоятельств, можно вместо композита обходиться декоратором, который хранит в себе элемент дерева + цепочку родителей, либо корень дерева + XPath, либо как-то еще.
I>>А по поподробнее можно ?
S>
Здравствуйте, Ikemefula, Вы писали:
I>Здравствуйте, samius, Вы писали:
I>>>А как тогда найти рутовый объект ? S>>Он известен.
I>Кому, тебе, Васе Пупкину ? Или ты предлагаешь протащить во все операции, свойства и вообще все внутренности параметр этого рута ?
Здравствуйте, samius, Вы писали:
I>>>>А как тогда найти рутовый объект ? S>>>Он известен.
I>>Кому, тебе, Васе Пупкину ? Или ты предлагаешь протащить во все операции, свойства и вообще все внутренности параметр этого рута ?
S>В таком духе продолжай сам с собой
Здравствуйте, Пельмешко, Вы писали: П>При этом в .NET я не встречал никаких средств обобщения, каких-нибудь специальных коллекций, осуществляющих слежение за инвариантом ссылок на Родителей.
П>Собственно я прошу как-то формализовать мой взгляд на проблему ссылок на Родителей, может кто подскажет годные статьи по теме, какие-нибудь шаблоны проектирования или альтернативные подходы, что-нибудь ещё...
В самих по себе ссылках на родителей и в инвариантах проблем вроде нет, можно реализовать любой необходимый механизм. Я показал один и з возможных случаев (можно добавлять только элемент без парента), но ситуации-то бывают разные, но всегда врое всё удавалось разрудить.
О том, как это влияет на композит подскажу только, что делать сложный объект как коллекцию других объектов кажется неверным: удобнее делать такой объект обычным объектом (не коллекцией) в котором будет свойство типа коллекции. Но к вопросам инварианта это вроде как отношения не имеет.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Пельмешко, Вы писали:
П>При этом в .NET я не встречал никаких средств обобщения, каких-нибудь специальных коллекций, осуществляющих слежение за инвариантом ссылок на Родителей. П>Собственно я прошу как-то формализовать мой взгляд на проблему ссылок на Родителей, может кто подскажет годные статьи по теме, какие-нибудь шаблоны проектирования или альтернативные подходы, что-нибудь ещё...