0

I have a basic library I created as follows:

(function () {
    function Store() {
        var store = [];

        if (!(this instanceof Store)) {
            return new Store();
        }

        this.add = function (name, price) {
            store.push(new StoreItem(name, price));
            return this;
        };
    }

    function StoreItem(name, price) {
        if (!(this instanceof StoreItem)) {
            return new StoreItem();
        }

        this.Name = name || 'Default item';
        this.Price = price || 0.0;
    }

    Store.prototype.toString = function () {
        // build a formatted string here
    };

    StoreItem.prototype.toString = function () {
        return this.Name + ' $' + this.Price;
    };

    window.shop = window.shop || {
        Store: function () {
            return new Store();
        }
    };
}());

The large majority of this works well! However, I do not want to expose my store array defined in the Store constructor as I do not want it modified in anyway outside this library's control.

But, on the contrary, I would like to override the Store's toString method to make use of the StoreItems in the store array so I can return a formatted string of all the StoreItems using its toString method.

E.g. if store was exposed, the toString method would look something like:

Store.prototype.toString = function () {
    return this.store.join('\r\n');
};

// shop.Store().add('Bread', 2).add('Milk', 1.5).toString() result:
// Bread $2
// Milk $1.5

Is there anyway I can achieve this without exposing my store array publicly?

keldar
  • 6,152
  • 10
  • 52
  • 82

2 Answers2

2

You can give each Store it's own .toString method, being privileged to access the local store variable through closure - just like you did with .add:

function Store() {
    if (!(this instanceof Store)) {
        return new Store();
    }
    var store = [];

    this.add = function(name, price) {
        store.push(new StoreItem(name, price));
        return this;
    };
    this.toString = function () {
        return store.join('\n');
    };
}

Alternatively, you will have to define some kind of accessor method anyway, or your store would be useless if you only can add to it but never read it. Something will need to display the store items, will need to iterate them, will need to test them for availability… If you create a generic accessor, maybe in form of some iterator (an each method with a callback?), then a .prototype.toString method could use that as well.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I don't get this... What on earth would prevent me from doing stuff like this: var bla = new Store(); bla.toString; <- contains the function... – OddDev Apr 21 '15 at 11:21
  • @OddDev: So what? The function is not the `store` array. OP wants to hide the `store`, not the `toString` method. – Bergi Apr 21 '15 at 11:23
  • Plus 1 – this seems to be the simplest way. – MrCode Apr 21 '15 at 11:42
1

It's possible to keep the prototype toString method whilst protecting store, by adding a public method that returns the stringified store, and calling it from Store.prototype.toString.

Below I added this.getStoreString to the constructor, and called it from the original toString.

(function () {
    function Store() {
        var store = [];

        if (!(this instanceof Store)) {
            return new Store();
        }

        this.add = function (name, price) {
            store.push(new StoreItem(name, price));
            return this;
        };

        // new method
        this.getStoreString = function(){
            return store.join('\r\n');  
        };
    }

    function StoreItem(name, price) {
        if (!(this instanceof StoreItem)) {
            return new StoreItem();
        }

        this.Name = name || 'Default item';
        this.Price = price || 0.0;
    }

    Store.prototype.toString = function () {
        // call the new method
        return this.getStoreString();
    };

    StoreItem.prototype.toString = function () {
        return this.Name + ' $' + this.Price;
    };

    window.shop = window.shop || {
        Store: function () {
            return new Store();
        }
    };
}());

Fiddle

@Bergi's answer seems simpler though, but this shows another option.

MrCode
  • 63,975
  • 10
  • 90
  • 112