我有一个人员类(Person class),还有两个继承类叫做父亲(Parent)和孩子(Child)。一个父亲可以有n个孩子,一个孩子也可以有n个父亲。
在面向对象设计中,最好的方式是如何创建父亲和孩子之间的引用关系?
我应该在每个类中创建一个列表来引用相关的父亲/孩子,还是有更好的方法?
很棒的问题。纯粹的多对多关系非常罕见,通常最好引入一个中间对象来模拟关系本身。如果(当!)出现需要捕获与关系相关的属性(例如子父关系是自然的、代理的、领养的等)的用例时,这将证明非常有价值。
因此,除了您已经确定的Person、Parent和Child实体之外,让我们引入一个名为ParentChildRelationship的对象。ParentChildRelationship的实例将具有对一个Parent和一个Child的引用,并且Parent和Child类都将保存这些实体的集合。
然后,最好确定您使用这些实体的用例,并添加适当的辅助方法来维护对象间的引用。在下面的示例中,我只选择向parent添加一个公共的AddChild方法。
public abstract class Person
{
}
public class Parent : Person
{
private HashSet<ParentChildRelationship> _children =
new HashSet<ParentChildRelationship>();
public virtual IEnumerable<ParentChildRelationship> Children
{
get { return this._children; }
}
public virtual void AddChild(Child child, RelationshipKind relationshipKind)
{
var relationship = new ParentChildRelationship()
{
Parent = this,
Child = child,
RelationshipKind = relationshipKind
};
this._children.Add(relationship);
child.AddParent(relationship);
}
}
public class Child : Person
{
private HashSet<ParentChildRelationship> _parents =
new HashSet<ParentChildRelationship>();
public virtual IEnumerable<ParentChildRelationship> Parents
{
get { return this._parents; }
}
internal virtual void AddParent(ParentChildRelationship relationship)
{
this._parents.Add(relationship);
}
}
public class ParentChildRelationship
{
public virtual Parent Parent { get; protected internal set; }
public virtual Child Child { get; protected internal set; }
public virtual RelationshipKind RelationshipKind { get; set; }
}
public enum RelationshipKind
{
Unknown,
Natural,
Adoptive,
Surrogate,
StepParent
}
public class Person
{
Person Parent { get;set; }
IList<Person> Children { get;set; }
}
当你不知道父级时,父级可以为空。 当你没有孩子时,孩子可以为空或为空列表。 由于每个孩子都是一个人,它可以有一个父母或自己的孩子。
这个设计本身很好,直到你提供了更详细的用例场景来说明它将如何被使用或持久化。
如果您能限制关联方向只走单向,那么您将避免很多麻烦(但这并不总是可能的)。
单向关系:
public class Parent : Person
{
public IEnumerable<Person> Children { get; }
}
public class Child : Person
{
public Parent Parent { get; }
}
然而,现在你有一个需要维护的循环引用,虽然这是可能的,但并不特别有效。
通常情况下,你可以通过让子元素触发事件而不是显式地引用它们的父元素来保持关联的单向关系。
IPerson
{
string Name {get; set;}
string LastName {get; set;}
// whatever else - such as sizeOfShoe, dob, etc
}
IHaveParents
{
// might wanna limit this to a fixed size
List<IPerson> Parents {get; set;}
}
IHaveChildren
{
List<IPerson> Children {get; set;}
}
IHaveSpouse
{
IPerson Spouse {get; set;}
}
public class DudeWithParentsAndChildren : IPerson, IHaveParents, IHaveChildren, IHaveSpouse
{
public void AskMoneyToParents(){throw new Exception("Implement me!");}
public void SlapChildren(){}
private void CheatOnSpouse(){}
// some other stuff that such a dude can do i.e. GoBowling
}
当新需求出现时(相信我,它们会出现),你可以轻松地以任何方式进行扩展。
更新: 因此,在您的情况下,如果您只想让子项拥有父项,反之亦然,您可以这样做:
public class Child : IPerson, IHaveParents
{
public void AskMoneyToParents(){throw new Exception("Implement me!");}
}
public class Parent : IPerson, IHaveChildren, IHaveSpouse
{
public void SlapChildren(){}
private void CheatOnSpouse(){}
// some other stuff that such a dude can do i.e. GoBowling
}
这样,如果你想要一个 IHaveFriends 接口,你可以实现它(这基本上强制实现者将 IPersons 列表作为名为 Friends 的属性公开)。如果你不需要它,就不要这样做,但是你可以很容易地通过添加接口来实现它,而其他所有东西都保持不变,这意味着你拥有一个相当不错的可扩展模型(不一定是最好的,你知道我的意思)。
class Person
{
readonly List<Person> _children = new List<Person>(),
_parents = new List<Person>();
public IEnumerable<Person> Children
{
get { return _children.AsReadOnly(); }
}
public IEnumerable<Person> Parents
{
get { return _parents.AsReadOnly(); }
}
public void AddChild(Person child)
{
_children.Add(child);
child._parents.Add(this);
}
public void AddParent(Person parent)
{
_parents.Add(parent);
parent._children.Add(this);
}
/* And so on... */
}