Skip to content

Instantly share code, notes, and snippets.

@getify
Last active January 7, 2024 11:58
Show Gist options
  • Save getify/5572383 to your computer and use it in GitHub Desktop.
Save getify/5572383 to your computer and use it in GitHub Desktop.
OLOO (objects linked to other objects) pattern explored (with comparison to the prototype style of the same code)
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
};
function Bar(who) {
Foo.call(this,"Bar:" + who);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar; // "fixes" the delegated `constructor` reference
Bar.prototype.speak = function() {
alert("Hello, " + this.identify() + ".");
};
var b1 = new Bar("b1");
var b2 = new Bar("b2");
b1.speak(); // alerts: "Hello, I am Bar:b1."
b2.speak(); // alerts: "Hello, I am Bar:b2."
// some type introspection
b1 instanceof Bar; // true
b2 instanceof Bar; // true
b1 instanceof Foo; // true
b2 instanceof Foo; // true
Bar.prototype instanceof Foo; // true
Bar.prototype.isPrototypeOf(b1); // true
Bar.prototype.isPrototypeOf(b2); // true
Foo.prototype.isPrototypeOf(b1); // true
Foo.prototype.isPrototypeOf(b2); // true
Foo.prototype.isPrototypeOf(Bar.prototype); // true
Object.getPrototypeOf(b1) === Bar.prototype; // true
Object.getPrototypeOf(b2) === Bar.prototype; // true
Object.getPrototypeOf(Bar.prototype) === Foo.prototype; // true
var Foo = {
Foo: function(who) {
this.me = who;
return this;
},
identify: function() {
return "I am " + this.me;
}
};
var Bar = Object.create(Foo);
Bar.Bar = function(who) {
// "constructors" (aka "initializers") are now in the `[[Prototype]]` chain,
// so `this.Foo(..)` works easily w/o any problems of relative-polymorphism
// or .call(this,..) awkwardness of the implicit "mixin" pattern
this.Foo("Bar:" + who);
return this;
};
Bar.speak = function() {
alert("Hello, " + this.identify() + ".");
};
var b1 = Object.create(Bar).Bar("b1");
var b2 = Object.create(Bar).Bar("b2");
b1.speak(); // alerts: "Hello, I am Bar:b1."
b2.speak(); // alerts: "Hello, I am Bar:b2."
// some type introspection
Bar.isPrototypeOf(b1); // true
Bar.isPrototypeOf(b2); // true
Foo.isPrototypeOf(b1); // true
Foo.isPrototypeOf(b2); // true
Foo.isPrototypeOf(Bar); // true
Object.getPrototypeOf(b1) === Bar; // true
Object.getPrototypeOf(b2) === Bar; // true
Object.getPrototypeOf(Bar) === Foo; // true
@chumlabs
Copy link

chumlabs commented Jun 26, 2017

This really makes sense. I think the key to understanding the value here is that the OLOO pattern makes use of delegation (something the JS prototype chain is really well-suited for), not inheritance (something that class-based languages use). The prototype chain does not really encourage an inheritance model. The OLOO pattern hints at a mental model that is much easier to reason about in JS. It's just linked objects; objects that can delegate (methods & variables) to other objects linked further up the chain. (no .prototype augmentation, no messy .constructor prop...). Need to initialize variables like a constructor does? create an init() method on the delegate.

@jfbilodeau
Copy link

Am I wrong in thinking that the main difference between 'prototype' and OLOO is that in 'prototype, the prototype is duplicated and in OLOO the prototype is linked?

@GodefroyClair
Copy link

@jfbilodeau :
The main problem when thinking through inheritance pattern while coding in JS is that it is NOT what you are doing when you use the constructor call mecanism (ie Factory function + new) or even when you use the new ES6 keywords like Class, inherits...
The reason is that the real inheritance pattern (the one implemented in java or C++ and the one you have in mind when you are reasoning) relies on copying where as the one implemented in js relies on linking.
So, at some point, you're gonna have problems. For starters, once made, "a copy is forever" whereas a link can get cut...
So it is much better to use OLOO because it doesn't "lie" about the true structure you are reliying on. It makes the "thinking" and "making" converge, it connects the abstract and the concrete.

@GeoDoo
Copy link

GeoDoo commented Dec 12, 2017

@GodefroyClair

The reason is that the real inheritance pattern ... relies on copying where as the one implemented in js relies on linking

Great explanation of the concept! Way to go!

@tnguven
Copy link

tnguven commented Feb 19, 2018

I prefer composition over inheritance. Carrying all the forest doesn't make sense.
The difference is INHERITANCE is when you design your types around what they are and COMPOSITION is when you design your types around what they do.

@blachawk
Copy link

blachawk commented Mar 1, 2018

I believe through this practice the key takeaway is Behavior Delegation of objects linking to other objects.

@BenceSzalai
Copy link

OLOO for the win. Also add this to Foo:

  [Symbol.hasInstance](instance) {
    return this.isPrototypeOf(instance)
  }

so this will work fine too:

b1 instanceof Bar; // true
b2 instanceof Bar; // true
b1 instanceof Foo; // true
b2 instanceof Foo; // true

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment