如何在类中交叉引用对象

11

我有一个人员类(Person class),还有两个继承类叫做父亲(Parent)和孩子(Child)。一个父亲可以有n个孩子,一个孩子也可以有n个父亲。

在面向对象设计中,最好的方式是如何创建父亲和孩子之间的引用关系?

我应该在每个类中创建一个列表来引用相关的父亲/孩子,还是有更好的方法?

5个回答

11

很棒的问题。纯粹的多对多关系非常罕见,通常最好引入一个中间对象来模拟关系本身。如果(当!)出现需要捕获与关系相关的属性(例如子父关系是自然的、代理的、领养的等)的用例时,这将证明非常有价值。

因此,除了您已经确定的Person、Parent和Child实体之外,让我们引入一个名为ParentChildRelationship的对象。ParentChildRelationship的实例将具有对一个Parent和一个Child的引用,并且Parent和Child类都将保存这些实体的集合。

然后,最好确定您使用这些实体的用例,并添加适当的辅助方法来维护对象间的引用。在下面的示例中,我只选择向parent添加一个公共的AddChild方法。

alt text

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
}

图片丢失了... 你能否把图片上传到imgur上,而不是引用Dropbox链接? - Sometowngeek

2
public class Person
{
    Person Parent { get;set; }
    IList<Person> Children { get;set; }
}

当你不知道父级时,父级可以为空。 当你没有孩子时,孩子可以为空或为空列表。 由于每个孩子都是一个人,它可以有一个父母或自己的孩子。

这个设计本身很好,直到你提供了更详细的用例场景来说明它将如何被使用或持久化。


1

如果您能限制关联方向只走单向,那么您将避免很多麻烦(但这并不总是可能的)。

单向关系:

public class Parent : Person
{
    public IEnumerable<Person> Children { get; }
}

如果你想让关联双向进行,也可以这样做:
public class Child : Person
{
    public Parent Parent { get; }
}

然而,现在你有一个需要维护的循环引用,虽然这是可能的,但并不特别有效。

通常情况下,你可以通过让子元素触发事件而不是显式地引用它们的父元素来保持关联的单向关系。


1
我想象一个孩子在未来也可以成为父母(如果他运气好的话...或者不幸,这取决于不同的观点),所以我会选择类似以下的内容:
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 的属性公开)。如果你不需要它,就不要这样做,但是你可以很容易地通过添加接口来实现它,而其他所有东西都保持不变,这意味着你拥有一个相当不错的可扩展模型(不一定是最好的,你知道我的意思)。


实际上,在我的示例中,子元素永远都不会成为父级元素,它早在那之前就已经被系统排除了 :-) - Zooking
很酷 - 但那只是一个例子,我想要传达的信息是,为了可扩展性,我仍然会创建Child和Parent类,并让它们分别实现IHaveParents和IHaveChildren(除了IPerson)。 - JohnIdol
好的,但在我的例子中,是否有可能只有接口IHaveChildOrParent? 我不是说这很好,只是想知道是否可以共享这两个接口,是否有任何优势。 - Zooking
如果我理解正确的话,根据你的情况,我会这样做。 - JohnIdol

1
正如JohnIdol所指出的那样,沿着某条线路的孩子可能会成为父母。换句话说,不要将父母和孩子子类化为人的子类。
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... */
}

是否选择引入具体的父类和子类,这实际上取决于正在开发的系统的性质。如果是家谱/家族树软件,则我同意您只需要一个具体的人类。但是,如果您正在开发用于跟踪儿童抚养费/福利支付的软件,则可能需要具体的父母和孩子类,因为这些实体具有非常不同的属性,并且孩子最终可以成为父母对该系统无关紧要。 - Ian Nelson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接