Более простой способ написать инкапсулированную родительскую / дочернюю структуру данных?
Время от времени я часто пишу структуру данных "родители" и "дети", где:
- Родитель имеет ссылки на от 0 до N различных детей.
- У ребенка есть ссылка на 0 родителей или 1 родителя.
- Ссылка должна быть взаимной. Для любого данного родителя любой дочерний элемент, на который он ссылается, должен также ссылаться на данного родителя обратно. Для любого данного потомка родитель, на который он ссылается, должен ссылаться на данного потомка обратно.
- Невозможно нарушить вышеуказанные правила, используя члены, доступные извне двух объявлений классов (не
private
), кроме использования Reflection.
Ментальные шаги, которые можно предпринять, прежде чем реализовать нечто подобное, могут начаться с чего-то вроде этого:
public class Parent
{
private readonly List<Child> _children = new List<Child>();
public readonly ReadOnlyCollection<Child> Children = _children.AsReadOnly();
}
public class Child
{
private Parent _parent;
public Parent Parent
{
get
{
return _parent;
}
set
{
if(value == _parent)
return;
if(_parent != null)
{
_parent._children.Remove(this);
_parent = null;
}
if(value != null)
{
value._children.Add(this);
_parent = value;
}
}
}
}
Конечно, это не скомпилируется, так как Parent._children
является private
, Но вы не захотите делать это чем-то, кроме личного, так как разрешите доступ за пределами Child
или же Parent
позволит нарушить правила в реализации или в другом месте.
Итак, решение, которое я придумал, - это гнездо Child
в Parent
, Вложенные классы могут получить доступ к закрытым членам класса, в который они вложены:
public class Parent
{
private readonly List<Child> _children = new List<Child>();
public readonly ReadOnlyCollection<Child> Children = _children.AsReadOnly();
public class Child
{
private Parent _parent;
public Parent Parent
{
get
{
return _parent;
}
set
{
if(value == _parent)
return;
if(_parent != null)
{
_parent._children.Remove(this);
_parent = null;
}
if(value != null)
{
value._children.Add(this);
_parent = value;
}
}
}
}
}
Вопрос, который я задаю, состоит в том, есть ли альтернативные способы написать это, которые достигают тех же целей, которые имеют меньше или меньше существенных недостатков, чем этот подход? Основные недостатки этого подхода:
- Это может привести к большим сценариям, хотя использование
partial
может помочь. - Это может привести к более глубокому вложению, чем хотелось бы.
- Это может привести к многословным именам классов. Чтобы получить доступ
Child
внеParent
, вы должны использоватьParent.Child
, В случаях, когда имена классов длинные, и особенно при использовании обобщений, это может привести к очень уродливому коду. - Использование обобщений (например, для обеспечения безопасности типов во время компиляции) может привести к путанице. Это, по крайней мере, частично связано с тем фактом, что
Child
является вложенным иParent<T1>.Child
это отличный тип отParent<T2>.Child
и когда вы хотите, чтобы безопасность типов была взаимной, это может привести к действительно уродливому использованию обобщенных типов или необходимости прибегнуть к использованию принудительной безопасности типов во время выполнения (хотя ее можно инкапсулировать, как правило, например, используя неуниверсальный реферат база гдеpublic
аксессоры вместоprotected
).
С другой стороны, это может быть хорошим примером того, где использование friend
расширить права доступа упростит эти проблемы!
3 ответа
Мне нравится использовать общий закрытый интерфейс для предоставления таких свойств:
public class SomeBigOuterClass {
private interface IChildFriend {
void SetParent(Parent parent);
}
public class Parent {
}
public class Child : IChildFriend {
void IChildFriend.SetParent(Parent parent) {
this.parent = parent;
}
private Parent parent;
}
}
Как я уже говорил в комментарии, добавьте метод Remove в родительский объект, и это, по крайней мере, не представит весь список детей. Что-то вроде:
public class Parent
{
private readonly List<Child> _children = new List<Child>();
public readonly ReadOnlyCollection<Child> Children = _children.AsReadOnly();
public void Remove(Child child)
{
if(child !=null)
{
_children.Remove(child);
}
}
}
И, конечно, как указано в комментариях и в предыдущем ответе, вам также нужно будет инкапсулировать добавление потомков к родителю, что потребует открытого Add(Child)
метод