35

I'm developing an object model that has lots of different parent/child classes. Each child object has a reference to its parent object. I can think of (and have tried) several ways to initialize the parent reference, but I find significant drawbacks to each approach. Given the approaches described below which is best ... or what is even better.

I'm not going to make sure the code below compiles so please try to see my intention if the code is not syntactically correct.

Note that some of my child class constructors do take parameters (other than parent) even though I don't always show any.

  1. Caller is responsible for setting parent and adding to the same parent.

    class Child {
      public Child(Parent parent) {Parent=parent;}
      public Parent Parent {get; private set;}
    }
    class Parent {
      // singleton child
      public Child Child {get; set;}
      //children
      private List<Child> _children = new List<Child>();
      public List<Child> Children { get {return _children;} }
    }
    

    Downside: setting parent is a two-step process for the consumer.

    var child = new Child(parent);
    parent.Children.Add(child);
    

    Downside: error prone. Caller can add child to a different parent than was used to initialize the child.

    var child = new Child(parent1);
    parent2.Children.Add(child);
    
  2. Parent verifies that caller adds child to parent for which was initialized.

    class Child {
      public Child(Parent parent) {Parent = parent;}
      public Parent Parent {get; private set;}
    }
    class Parent {
      // singleton child
      private Child _child;
      public Child Child {
        get {return _child;}
        set {
          if (value.Parent != this) throw new Exception();
          _child=value;
        }
      }
      //children
      private List<Child> _children = new List<Child>();
      public ReadOnlyCollection<Child> Children { get {return _children;} }
      public void AddChild(Child child) {
        if (child.Parent != this) throw new Exception();
        _children.Add(child);
      }
    }
    

    Downside: Caller still has a two-step process for setting parent.

    Downside: runtime checking – reduces performance and adds code to every add/setter.

  3. The parent sets the child's parent reference (to itself) when the child is added/assigned to a parent. Parent setter is internal.

    class Child {
      public Parent Parent {get; internal set;}
    }
    class Parent {
      // singleton child
      private Child _child;
      public Child Child {
        get {return _child;}
        set {
          value.Parent = this;
          _child = value;
        }
      }
      //children
      private List<Child> _children = new List<Child>();
      public ReadOnlyCollection<Child> Children { get {return _children;} }
      public void AddChild(Child child) {
        child.Parent = this;
        _children.Add(child);
      }
    }
    

    Downside: The child is created without a parent reference. Sometimes initialization/validation requires the parent which means some initialization/validation must be performed in the child’s parent setter. The code can get complicated. It would be so much easier to implement the child if it always had its parent reference.

  4. Parent exposes factory add methods so that a child always has a parent reference. Child ctor is internal. Parent setter is private.

    class Child {
      internal Child(Parent parent, init-params) {Parent = parent;}
      public Parent Parent {get; private set;}
    }
    class Parent {
      // singleton child
      public Child Child {get; private set;}
      public void CreateChild(init-params) {
          var child = new Child(this, init-params);
          Child = value;
      }
      //children
      private List<Child> _children = new List<Child>();
      public ReadOnlyCollection<Child> Children { get {return _children;} }
      public Child AddChild(init-params) {
        var child = new Child(this, init-params);
        _children.Add(child);
        return child;
      }
    }
    

    Downside: Can’t use initialization syntax such as new Child(){prop = value}. Instead have to do:

    var c = parent.AddChild(); 
    c.prop = value;
    

    Downside: Have to duplicate the parameters of the child constructor in the add-factory methods.

    Downside: Can’t use a property setter for a singleton child. It seems lame that I need a method to set the value but provide read access via a property getter. It’s lopsided.

  5. Child adds itself to the parent referenced in its constructor. Child ctor is public. No public add access from parent.

    //singleton
    class Child{
      public Child(ParentWithChild parent) {
        Parent = parent;
        Parent.Child = this;
      }
      public ParentWithChild Parent {get; private set;}
    }
    class ParentWithChild {
      public Child Child {get; internal set;}
    }
    
    //children
    class Child {
      public Child(ParentWithChildren parent) {
        Parent = parent;
        Parent._children.Add(this);
      }
      public ParentWithChildren Parent {get; private set;}
    }
    class ParentWithChildren {
      internal List<Child> _children = new List<Child>();
      public ReadOnlyCollection<Child> Children { get {return _children;} }
    }
    

    Downside: calling syntax is not great. Normally one calls an add method on the parent instead of just creating an object like this:

    var parent = new ParentWithChildren();
    new Child(parent); //adds child to parent
    new Child(parent);
    new Child(parent);
    

    And sets a property rather than just creating an object like this:

    var parent = new ParentWithChild();
    new Child(parent); // sets parent.Child
    

...

I just learned that SE does not allow some subjective questions and clearly this is a subjective question. But, maybe it is a good subjective question.

  • 13
    Best practice is that children should not know about their parents. – Telastyn Oct 31 '14 at 15:20
  • 4
    please don't cross-post: stackoverflow.com/questions/26643528/… – gnat Oct 31 '14 at 15:24
  • 2
    @Telastyn I can't help but read that as tongue in cheek, and it's hilarious. Also completely dead bloody accurate. Steven, the term to look into is "acyclic" as there's plenty of literature out there on why you should make graphs acyclic if at all possible. – Jimmy Hoffa Oct 31 '14 at 15:26
  • 9
    @Telastyn you should try to use that comment on parenting.stackexchange – Fabio Marcolini Oct 31 '14 at 22:02
  • 2
    Hmm. Don't know how to move a post (don't see a flag control). I re-posted to programmers since someone told me it belonged there. – Steven Broshar Nov 4 '14 at 2:08
17

I would stay away from any scenario which necessarily requires the child to know about the parent.

There are ways of passing messages from child to parent through events. This way the parent, upon add, simply has to register to an event that the child triggers, without the child having to know about the parent directly. Afterall, this is likely the intended usage of a child knowing about its parent, in order to be able to use the parent to some effect. Except you wouldn't want the child performing the job of the parent, so really what you want to do is simply tell the parent that something has happened. Therefore, what you need to handle is an event on the child, of which the parent can take advantage.

This pattern also scales very well should this event become useful to other classes. Perhaps it is a bit of an overkill, but it also prevents you from shooting yourself in the foot later, since it becomes tempting to want to use parent in your child class which only couples the two classes even more. Refactoring of such classes afterwards is time-consuming and can easily create bugs in your program.

Hope that helps!

  • 5
    stay away from any scenario which necessarily requires the child to know about the parent” – why? Your answer hinges on the assumption that circular object graphs are a bad idea. While this is sometimes the case (e.g. when memory-managing via naive ref-counting – not the case in C#), it's not a bad thing in general. Notably, the Observer Pattern (which is often used to dispatch events) involves the observable (Child) maintaining a set of observers (Parent), which reintroduces the circularity in a backwards way (and introduces a number of issues of its own). – amon Nov 4 '14 at 8:58
  • 1
    Because cyclic dependencies means structuring your code in such a way that you can't have one without the other. By the nature of parent-child relationship, they should be separate entities or else you risk having two tightly coupled classes which might as well be a single gigantic class with a list for all the careful care put into its design. I fail to see how Observer pattern is the same as parent-child other than the fact that one class has references to several others. For me parent-child is a parent having a strong dependency on the child, but not the inverse. – Neil Nov 4 '14 at 17:45
  • I agree. Events are the best way to handle parent-child relationship in this case. It's the pattern I use very often, and it makes the code very easy to maintain, instead of having to worry about what the child class is doing to the parent through the reference. – Eternal21 Nov 5 '14 at 0:14
  • @Neil: Mutual dependencies of object instances form a natural part of many data models. In a mechanical simulation of a car, different parts of the car will need to transmit forces to each other; this is typically better handled by having all of the parts of the car regard the simulation engine itself as a "parent" object, than by having cyclic dependencies among all the components, but if components are able to respond to any stimuli from outside the parent they'll need a way to notify the parent if such stimuli have any effects the parent needs to know about. – supercat Dec 6 '14 at 17:25
  • 2
    @Neil: If the domain includes a forest objects with irreducible cyclic data dependencies, any model of the domain will do so as well. In many cases this will further imply that the forest will behave as a single giant object whether one wants it to or not. The aggregate pattern serves to concentrate the complexity of the forest into a single class object called the Aggregate Root. Depending upon the complexity of the domain being modeled, that aggregate root may get somewhat large and unwieldy, but if complexity is unavoidable (as is the case with some domains) it's better... – supercat Dec 10 '14 at 16:22
10

I think your option 3 might be the cleanest. You wrote.

Downside: The child is created without a parent reference.

I don't see that as a downside. In fact, the design of your program can benefit from child objects which can be created without a parent first. For example, it can make testing childs in isolation a lot easier. If a user of your model forgets to add a child to its parent and calls a methods which expects the parent property initialized in your child class, then he gets a null ref exception - which is exactly what you want: an early crash for a wrong usage.

And if you think a child needs its parent attribute to be initialized in the constructor under all circumstances for technical reasons, use something like a "null parent object" as the default value (though this has the risk of masking errors).

  • If a child object can't do anything useful without a parent, having a child object start without a parent will require it to have a SetParent method which will either need to support changing the parentage of an existing child (which may be hard to do and/or nonsensical) or will only be callable once. Modeling such situations as aggregates (as Mike Brown suggests) can be much better than having children start without parents. – supercat Dec 4 '14 at 4:30
  • If a use case calls for something even once, the design must allow for it always. Then, restricting that capability to special circumstances is easy. Adding such a capability afterwards however is usually impossible. The best solution is Option 3. Probably with a third, "Relationship" object between the parent and child such that [Parent] ---> [Relationship(Parent owns Child)] <--- [Child]. This also allows multiple [Relationship] instances such as [Child] ---> [Relationship(Child is owned by Parent)] <--- [Parent]. – DocSalvager Jan 8 '16 at 9:02
3

There's nothing to prevent high cohesion between two classes that are used commonly together (e.g. an Order and LineItem will commonly reference each other). However, in these cases, I tend to adhere to Domain Driven Design rules and model them as an Aggregate with the Parent being the Aggregate Root. This tells us that the AR is responsible for the lifetime of all objects in its aggregate.

So it would be most like your scenario four where the parent exposes a method to create its children accepting any necessary parameters to properly initialize the children and adding it to the collection.

  • 1
    I would use a slightly-looser definition of aggregate, which would allow for the existence of outside references into parts of the aggregate other than the root, provided that--from the point of view of an outside observer--the behavior would be consistent with every part of the aggregate holding only a reference to the root and not to any other part. To my mind, the key principle is that each mutable object should have one owner; an aggregate is a collection of objects which are all owned by a single object (the "aggregate root"), which should know of all references that exist to its parts. – supercat Dec 10 '14 at 19:24
3

I would suggest having "child-factory" objects which are passed to a parent method which creates a child (using the "child factory" object), attaches it, and returns a view. The child object itself will never be exposed outside the parent. This approach can work nicely for things like simulations. In an electronics simulation, one particular "child factory" object might represent the specifications for some kind of transistor; another might represent the specifications for a resistor; a circuit which needs two transistors and four resistors might be created with code like:

var q2N3904 = new TransistorSpec(TransistorType.NPN, 0.691, 40);
var idealResistor4K7 = new IdealResistorSpec(4700.0);
var idealResistor47K = new IdealResistorSpec(47000.0);

var Q1 = Circuit.AddComponent(q2N3904);
var Q2 = Circuit.AddComponent(q2N3904);
var R1 = Circuit.AddComponent(idealResistor4K7);
var R2 = Circuit.AddComponent(idealResistor4K7);
var R3 = Circuit.AddComponent(idealResistor47K);
var R4 = Circuit.AddComponent(idealResistor47K);

Note that the simulator need not retain any reference to the child-creator object, and AddComponent won't return a reference to the object created and held by the simulator, but rather an object that represents a view. If the AddComponent method is generic, the view object could include component-specific functions but would not expose the members which the parent uses to manage the attachment.

2

Great listing. I do not know which method is the "best" but here is one to find the most expressive methods.

Start with the simplest possible parent and child class. Write your code with those. Once you notice code duplication that can be named you put that into a method.

Maybe you get addChild(). Maybe you get something like addChildren(List<Child>) or addChildrenNamed(List<String>) or loadChildrenFrom(String) or newTwins(String, String) or Child.replicate(int).

If your problem is really about forcing a one-to-many relationship maybe you should

  • force it in the setters which may lead to confusion or throw-clauses
  • you remove the setters and create special copy or move methods - which is expressive and understandable

This is not an answer but I hope you find one while reading this.

0

I appreciate that having links from child to parent had it's downsides as noted above.

However for many scenarios the workarounds with events and other "disconnected" mechanics also bring their own complexities and extra lines of code.

For example raising an event from Child to be received by it's Parent will bind both together albeit in a loose coupling fashion.

Perhaps for many scenarios it is clear to all developers what a Child.Parent property means. For the majority of systems I worked on this has worked just fine. Over engineering can be be time consuming and…. Confusing!

Have a Parent.AttachChild() method that performs all the work needed to bind the Child to its parent. Everybody is clear on what this "means"

protected by gnat Sep 5 '18 at 0:18

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.