Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle class object with circular references?

I am thinking about simple problem. I have given an class for example Model

class Model {
    constructor(parameters = {}) {
        this.id = parameters.id;
    }
}

so as you can see we can create new Model objects like: let model = new Model(). More complex example would look like this:

//we have some data given from API maybe?
let parameters = {id: 1};
let model = new Model(parameters );

And here we are at the point where i started to wander What if the object with given id already exists ?

The question is what pattern should i use to instantiate object with given id only once?

Let's go further: what if we will get nested objects with circural references ? Let assume we got another class called AnotherModel and our code looks like:

class Model {
    constructor(parameters = {}) {
        this.id = parameters.id;
        this.anotherModel= nulld;

        if (parameters.anotherModel) {
            this.anotherModel= parameters.anotherModel instanceof AnotherModel
                ? parameters.anotherModel
                : new AnotherModel(parameters.anotherModel);
        }
    }
}

class AnotherModel {
    constructor(parameters = {}) {
        this.id = parameters.id;
        this.models = [];

        if (parameters.models) {
            for (let i = 0; i < parameters.models.length; i++) {
                let model = parameters.models[i];
                model.anotherModel= this;
                this.models.push(new Model(model));
            }
        }
    }
}

So AnotherModel contains a collection of Models and Model object contains reference to AnotherModel.

What is the nice way to resolve this issue ? What we want to achive is to have only one object with the same id.

What i was thinking is to do some kind of ObjectPool where i will store all objects for given class or classes and when new object is instantiated our pool would create a new one if it does not exist or return the existing one?

But here is a little disadventage, if for example we already have written some code we would have to refactore and change the way we instatiate them from new Model() to ObjectPool.get(Model, parameters)?

What are your ideas ?

like image 261
vardius Avatar asked Apr 28 '16 11:04

vardius


1 Answers

You could use an object pool (either on the class or outside of it) to keep track of your instances. By defining it in the constructor, you can still instantiate models with:

new Model();
new AnotherModel();

If the id already exists in the pool, you can just return the existing instance.

Outside of the class:

const modelPool = {};

class Model {
    constructor(parameters = {}) {
        if (modelPool[parameters.id] instanceof Model) {
            return modelPool[parameters.id];
        }

        modelPool[parameters.id] = this;

        this.id = parameters.id;
        this.anotherModel= null;

        // ...
    }
}

const anotherModelPool = {};

class AnotherModel {
    constructor(parameters = {}) {
        if (anotherModelPool[parameters.id] instanceof AnotherModel) {
            return anotherModelPool[parameters.id];
        }

        anotherModelPool[parameters.id] = this;

        this.id = parameters.id;
        this.models = [];

        //...
    }
}

Or as a (non-enumerable, non-writeable, non-configurable) property on the class (not the instance):

class Model {
    constructor(parameters = {}) {
        if (Model.pool[parameters.id] instanceof Model) {
            return Model.pool[parameters.id];
        }

        Model.pool[parameters.id] = this;

        this.id = parameters.id;
        this.anotherModel= null;

        //...
    }
}

Object.defineProperty(Model, 'pool', {
    value: {}
});

class AnotherModel {
    constructor(parameters = {}) {
        if (AnotherModel.pool[parameters.id] instanceof AnotherModel) {
            return AnotherModel.pool[parameters.id];
        }

        AnotherModel.pool[parameters.id]

        this.id = parameters.id;
        this.models = [];

        //...
    }
}

Object.defineProperty(AnotherModel, 'pool', {
    value: {}
});

As added by @Vardius, one can also create a pseudo-abstract class (as JS does not have abstract classes) which can be extended from. Using new.target.name, a namespace within the pool of the abstract class can be created:

class Entity {
    constructor(parameters = {}) {
        if (Entity.pool[this.constructor.name] && Entity.pool[this.constructor.name][parameters.id] instanceof Entity) {
            return Entity.pool[new.target.name][parameters.id];
        }
        Entity.pool[new.target.name][parameters.id] = this;
    }
}
Object.defineProperty(Entity, 'pool', {value: {} });
like image 173
nils Avatar answered Nov 10 '22 07:11

nils