45

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.

amon
  • 132,749
  • 27
  • 279
  • 375
Steven Broshar
  • 575
  • 1
  • 4
  • 4
  • 16
    Best practice is that children should not know about their parents. – Telastyn Oct 31 '14 at 15:20
  • 4
    please don't **[cross-post](http://meta.stackexchange.com/tags/cross-posting/info "'Cross-posting is frowned upon...'")**: http://stackoverflow.com/questions/26643528/what-is-the-best-way-to-initialize-a-childs-reference-to-its-parent – 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
  • 11
    @Telastyn you should try to use that comment on parenting.stackexchange – Fabio Marcolini Oct 31 '14 at 22:02
  • 3
    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 04 '14 at 02:08

6 Answers6

24

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!

Neil
  • 22,670
  • 45
  • 76
  • 7
    “*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 04 '14 at 08: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 04 '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 05 '14 at 00: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 06 '14 at 17:25
  • @Neil: The parent *object instance* would get very big and complicated, but that doesn't imply that the parent *class* itself would need to get big and unwieldy. The parent class would need to include methods to handle attachment, detachment, and message dispatching, but much of the simulation work could be done by code in other classes [e.g. a `GravitationalField` class might ask the main simulation object to relay an "accelerate by 9.8m/s^2" message to all the movable mechanical components it contained. – supercat Dec 06 '14 at 17:30
  • @supercat Your car analogy is correct up to a certain point. The hubcap on a wheel depends on the wheel, but not the other way around. The gasoline depends on the gasoline tank, but not the other way around. The whole point of a parent-child relationship is to organize the concept of one entity depending on another but not vice versa. Why even attempt parent-child relationship if you intend to implement it badly? I'm not saying such cyclic dependencies between components don't exist, though hopefully you agree that they aren't "parent-child" relationships. – Neil Dec 10 '14 at 08:22
  • @supercat I disagree with your sentiment that the parent class wouldn't get big and unwieldy, though supposing you somehow managed it, you've only slapped lipstick on a pig. You're forcing a pattern to meet your other requirements, which is a big no-no. At that point, you simply *don't* apply any pattern at all, or at the very least, don't call it parent-child. – Neil Dec 10 '14 at 08:29
  • 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
  • ...to have it be *visible* in an aggregate root object which owns all the others, than to have it be hidden among all the objects in the forest. If an aggregate root "knows" about all dependencies among the objects within the aggregate, such dependencies may be unraveled by focusing on the aggregate root. If some objects can have dependencies the aggregate root doesn't know about, unraveling such dependencies will require examining *all* such objects--generally a much larger task. – supercat Dec 10 '14 at 16:33
  • So to concretize @supercat 's description (correct me if I am misconstruing it), an Excel.Range, Worksheet, and Workbook are tightly coupled to each other by their two-way parent-child relationship and effectively behave as one large aggregate root object. This can be desirable if it models the problem domain in a useful way, but limits one from having a Range that is free to exists outside of a Worksheet, or a Worksheet outside of a workbook. The benefit is that you can climb the model freely up and down. Neil considers that this not parent-child, Mike Brown defines this as Aggregate pattern. – Mike Jan 16 '15 at 00:48
  • @supercat I would argue that such complexity as you call it is always avoidable. Perhaps it is difficult to get a god object to interact with other parts of your program, but therein is the problem. What changes is our ability to understand the relationships between such objects that makes it difficult to fit into easy-to-understand models like parent-child. The closest analogy that comes to mind is the "many-to-many" relationship in a database. It can always be simplified into 3 tables with "many-to-one" and "one-to-many" relationships. However, if one doesn't know that... – Neil Jan 16 '15 at 09:42
  • ...you wind up with a overly complicated database which only gets further complicated when you have to have to add new tables. The problem is a priori. – Neil Jan 16 '15 at 09:43
  • @Neil: I'm not saying it's difficult to get a god object to interact with other parts of the program, but rather that the *lack* of a god object for the aggregate would make things difficult for other parts of the program that need to interact with the aggregate as a whole. Further, while it's possible to flatten many-to-many dependencies, doing so can pose a major performance hit. If one member of an aggregate holds a direct reference to another, that may be accessed with a single indirection. If it instead holds a surrogate key for a table held by its parent... – supercat Jan 16 '15 at 16:24
  • ...managing the relationship may be easier, but access will require an indirection to reach the parent, an indirection to reach the table, and at least one indirection to find the item within the table, in addition to the indirection to read the item itself. – supercat Jan 16 '15 at 16:26
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).

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
  • 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 04 '14 at 04: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 08 '16 at 09: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.

Michael Brown
  • 21,684
  • 3
  • 46
  • 83
  • 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.

supercat
  • 8,335
  • 22
  • 28
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.

User
  • 795
  • 4
  • 9
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"

Rax
  • 111
  • 1