Search code examples
javascriptoopprivate-membersbidirectionaldata-consistency

How can I maintain consistency in a 2-way object relationship in JavaScript?


If I have a 2-way relationship between 2 objects, such that A relates to B and B relates to A, how can I keep this consistent such that the 2 objects will always refer back to each other?

I'm struggling to put my very simple problem into words, so here's a very simple example. I start with Husband and Wife:

function Husband() { this.wife; }
function Wife() { this.husband; }

var harry = new Husband();
var wendy = new Wife();

harry.wife = wendy;
wendy.husband = harry;

Logically, if Harry's wife is Wendy, then Wendy's husband is Harry.

I need a way of keeping this relationship consistent. So I create a setter method on Husband and denote that the wife variable should be treated as private by prefixing with an underscore.

function Husband() {
    this._wife;
    this.setWife = function(wife) {
        this._wife = wife;
        wife.husband = this;
    }
}

Describing this relationship is now simple and encourages consistency:

harry.setWife(wendy);

Likewise, it would be nice to have the reverse option:

wendy.setHusband(harry);

To do this I create a setHusband method on Wife, and tweak Husband as needed.

function Husband() {
    this._wife;
    this.setWife = function(wife) {
        this._wife = wife;
        // wife.husband = this; // <-- husband is now _husband (private)...
        wife.setHusband(this);  // <-- switching to a public method
    }
}

function Wife() {
    this._husband;
    this.setHusband = function(husband) {
        this._husband = husband;
        husband._wife = this;  // <-- Oops! _wife is private!
        husband.setWife(this); // <-- Oops! this results in an infinite loop!
    }
}

At this point I run into a snag. My new setHusband method needs to be able to maintain consistency, but wife is now _wife (private), and calling setWife causes an infinite loop because they reciprocate each other.

I could create another set of methods like reallyJustSetHusband but that seems silly.

My conundrum isn't particular to JavaScript, but I've mentioned it in the question in case there's a particular approach needed.

What is the best way to achieve consistency between these 2 objects? Is there something I've overlooked?

Similar Pattern in the DOM

In the DOM, if you call parent.appendChild(child), then child.parentNode === parent. They are never inconsistent. If a parent has a child the child has the same parent. Other relationships such as nextSibling are also kept consistent.


Solution

  • A simple way is to just check for redundant values and abort early:

    function Husband() {
        this._wife;
        this.setWife = function(wife) {
            if(this._wife == wife) return; // prevents recursion
            this._wife = wife;
            wife.setHusband(this);
        }
    }
    
    function Wife() {
        this._husband;
        this.setHusband = function(husband) {
            if(this._husband == husband) return; // prevents recursion
            this._husband = husband;
            husband.setWife(this); 
        }
    }
    

    you could also use an external state manager (redux, sql, etc) with update events, or have direct properties that don't need setters and be careful about keeping the data updated.