Expand your dish

I learned more from the one restaurant piece of code that didn’t work than from all the ones that were successes

Based on Wolfgang Puck quote

Let’s say we want to operate on a list of class instances. For the sake of the example we will order some dished from a restaurant, and eat them all:

class Dish{
eatIt(){}
}

..
..
..

function eatAllYouCan(dishes){
dishes.forEach(x => {
x.eatIt();
});
}

The challenge

But one day we found out that we need to add plenty of conscious constraint before eating the dish. Since we operate outside of the scope of the dish, the code will look something like:

Basic implementation

function eatAllYouCan(moralConstraint,dishes){
dishes.forEach(dish => {
if(passMoralConstrain(moralConstraint,dish)){
x.eatIt();
}
});
}

And will refer to something like:

function isVegen(dish){
return isVegetarian(dish) && !dish.ingredientsContains(['eags',...]);
}

function isVegetarian(dish){
return !dish.wasAlive();
}

function isKosher(dish){
return dish.ingredientsContains.length == 0; // Ha Ha
}

function passMoralConstrain(moralConstraint,dish){
switch(moralConstraint){
case 'vegan': return isVegan(dish);
case 'vegetarian': return isVegetarian(dish);
case 'kosher': return isKosher(dish);
default: return true;
}
}

The problem with the code is that since we operate on the Dish, we prefer to operate on the dish itself, and not to pass it as a value:

function eatAllYouCan(moralConstraint,dishes){
dishes.forEach(dish => {
if(dish.passMoralConstrain(moralConstraint)){
x.eatIt();
}
});
}

function passMoralConstrain(moralConstraint){
switch(moralConstraint){
case 'vegan': return dish.isVegan();
case 'vegetarian': return dish.isVegetarian();
case 'kosher': return dish.isKosher();
default: return true;
}
}

So we need to find a way to extend the dishes.

Enrich each object

The most simple and worse way is to enrich each and every object. It inefficient and repetitive, but works:

function prepareDishes(dishes){
dishes.forEach(dish => {
dish.passMoralConstrain = passMoralConstrain;
dish.isVegetarian = isVegetarian;
dish.isVegan = isVegan;
dish.isKosher = isKosher;
});
}

Extend the class

A better way will be to extend the class, this way each intense will have the same function thanks to the prototype chain:

function prepareDishes(dishes){
Dish.prototype.passMoralConstrain = passMoralConstrain;
Dish.prototype.isVegetarian = isVegetarian;
Dish.prototype.isVegan = isVegan;
Dish.prototype.isKosher = isKosher;
}

Hijack the __proto__

But what if we need something generic, and we don’t want to rely on the base class. We still can hijack the prototype:

function prepareDishes(dishes){
let proto = dishes[0].__proto__;
proto.passMoralConstrain = passMoralConstrain;
proto.isVegetarian = isVegetarian;
proto.isVegan = isVegan;
proto.isKosher = isKosher;
}

The problem here is that __proto__ has three large red warnings on the MDN web docs and is on removing process.

Ask for the prototype nicely

A wise man asked: why to have an unsafe hijack if you can ask nicely? That’s why ES6 added the getPrototypeOf method:

function prepareDishes(dishes){
let prototype = Object.getPrototypeOf(dishes[0])
prototype.passMoralConstrain = passMoralConstrain;
prototype.isVegetarian = isVegetarian;
prototype.isVegan = isVegan;
prototype.isKosher = isKosher;
}

Same as before, just that the previous one will not work on new browsers and the last will not work on old ones :-~