tags:

views:

127

answers:

5

Hi,

I have a Person class and two inherited classes called Parent and Child. A Parent can have n Child(s) and a Child can have n Parent(s).

What is the best way in OOD to create a reference between a Parent and a Child.

Should I create a List in each class referencing the connected Parent/Child or is there a better way?

BR Larre

+1  A: 

If you can limit the direction of the association to go only one way, you will save yourself a lot of trouble (but this is not always possible).

One-way relationship:

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

If you want to have the association going the other direction as well, you can do so too:

public class Child : Person
{
    public Parent Parent { get; }
}

However, now you have a circular reference that you need to maintain, and while it's possible, it's not particularly productive.

You can often keep the association as a one-way relationship by letting the children raise events instead of explicitly referencing their parent.

Mark Seemann
+1  A: 

I would imagine that a child can also be a parent down the line (if he gets lucky ... or unlucky, depending on points of view) so I would go with something like:

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
}

And you could easily extend this any way you like when new requirements come along (trust me they will).

Update: So in your case if you only want a Child to have Parents and the other way around you'd do something like:

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
}

This way if you want to have an IHaveFriends interface you can (which basically forces the implementer to expose a list of IPersons as a property named Friends). If you don't need it don't do it, but the fact that you can easily do it just adding an interface an everything else stays the same means you've got a pretty decent extensible model (not necessarily the best, you know what I mean).

JohnIdol
Acctually in my example the child will never become a parent, it is thrown out of the system long before that :-)
Zooking
cool - but that's only an example, the message I am trying to get across is that I would still create Child and Parent classes having them implement respectively those IHaveParents and IHaveChildren (other than IPerson) for extensibility.
JohnIdol
Ok, but in my example would it be possible to just have the Interface IHaveChildOrParent? I am not saying that it would be nice just wondering if it would be possible to share the two and if there is any advantages.
Zooking
see edit - if I understand correctly this is what I would do in your case
JohnIdol
+1  A: 

As JohnIdol pointed out, a child someway down the line might become a parent. In other words DON'T make Parent and Child sub-classes of Person.

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... */
}
Robert Davis
Whether you choose to introduce concrete Parent and Child classes really depends on the nature of the system being developed.If it's genealogy / family tree software, then I agree you would want only a concrete Person class.But if you're developing software to track child maintenance / benefit payments, then concrete classes for Parent and Child would likely be required, as these entities would have quite different attributes, and the fact that a child could eventually become a parent themself would be irrelevant to the system.
Ian Nelson
+1  A: 
public class Person
{
    Person Parent { get;set; }
    IList<Person> Children { get;set; }
}

Parent can be null when you do not know the parent. Children can be null or empty when you have no children. Since each child is a Person, it can have a Parent or its own children.

This design, by itself, is fine until you provide more detailed use case scenarios about how it will be used or persisted.

Thomas
+4  A: 

Great question. Pure many-to-many relationships are actually quite rare, and it usually helps to introduce an intermediate object to model the relationship itself. This will prove invaluable if (when!) use cases emerge which require the capture of properties regarding the relationship (e.g. whether the child/parent relationship is natural, surrogate, adoptive, etc).

So, in addition to the Person, Parent and Child entities which you've already identified, let's introduce an object called ParentChildRelationship. An instance of ParentChildRelationship will have a reference to exactly one Parent and One Child, and both the Parent and Child classes will hold a collection of these entities.

It's a good idea to then identify the use cases you have for working with these entities, and add appropriate helper methods to maintain the inter-object references. In the example below I've just chosen to add a public AddChild method to the parent.

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
}
Ian Nelson