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?
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.
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; }
}
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.
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.
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... */
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With