Encapsulation and Composition - JavaScript Spessore: A thick shot of objects, metaobjects, & protocols (2014)

JavaScript Spessore: A thick shot of objects, metaobjects, & protocols (2014)

Encapsulation and Composition

26

In This Chapter

In this chapter, we’ll extend our look at encapsulating metaobjects and dive deeper into the use of proxies and partial proxies to decouple metaobjects from each other.

The Encapsulation Problem

The central form of encapsulation in object-oriented programming consists of objects that invoke methods on each other. Each object’s method bodies invoke other methods and/or access the object’s own private state. Objects do not read or update each other’s private state.

Objects and behaviour have dependencies, but the scope of every dependency is explicitly limited to the methods exposed and implicitly limited to the changes of state that can be observed through the methods exposed.

Unlike Smalltalk, Ruby, or many other OO languages, JavaScript does not enforce this encapsulation: Objects are free to read and update each other’s properties.

the closure workaround

It’s possible to use a closure to create private state. We saw this in JavaScript Allongé:

var stack = (function () {

var array = [],

index = -1;

return {

push: function (value) {

array[index += 1] = value

},

pop: function () {

var value = array[index];

if (index >= 0) {

index -= 1

}

return value

},

isEmpty: function () {

return index < 0

}

}

})();

This approach has several related problems:

1. A separate instance of each function must be created for each object.

2. It is not open for extension. You cannot define another stack method elsewhere that uses the “private” variables array or index.

3. You cannot use this directly in a shared prototype.

4. Since the private state is captured in variables, you cannot take advantage of property access features like iterating over properties.

5. You can’t vary privacy separately from methods. The code must be written one way for normal methods and another way for methods with private state.

There are workarounds for these problems, but we must be mindful of its limitations.

private mixins

The private mixin pattern addresses some of the problems with the closure workaround:

function extendPrivately (receiver, mixin) {

var methodName,

privateProperty = Object.create(null);

for (methodName in mixin) {

if (mixin.hasOwnProperty(methodName)) {

receiver[methodName] = mixin[methodName].bind(privateProperty);

};

};

return receiver;

};

var HasCareer = {

career: function () {

return this.chosenCareer;

},

setCareer: function (career) {

this.chosenCareer = career;

return this;

}

};

var somebody = {};

extendPrivately(somebody, HasCareer);

Privacy and method concerns are separated. And the private properties are in an object so they behave like all properties. But our private state is still not open for extension, and a private mixin can only be employed in a prototype chain with a workaround of its own. And the private properties are in a separate object from the public properties, so it is not possible to access both at the same time.

Proxies

As we recall from proxies, there is a simple pattern for encapsulation that solves many of the problems we saw earlier: We use forwarding to create a proxy.

function proxy (baseObject, optionalPrototype) {

var proxyObject = Object.create(optionalPrototype || null),

methodName;

for (methodName in baseObject) {

if (typeof(baseObject[methodName]) === 'function') {

(function (methodName) {

proxyObject[methodName] = function () {

var result = baseObject[methodName].apply(baseObject, arguments);

return (result === baseObject)

? proxyObject

: result;

}

})(methodName);

}

}

return proxyObject;

}

var stack = {

array: [],

index: -1,

push: function (value) {

return this.array[this.index += 1] = value;

},

pop: function () {

var value = this.array[this.index];

this.array[this.index] = void 0;

if (this.index >= 0) {

this.index -= 1;

}

return value;

},

isEmpty: function () {

return this.index < 0;

}

};

var stackProxy = proxy(stack);

Our stackProxy has methods, but no private data:

stackProxy.push('first');

stackProxy

//=>

{ push: [Function],

pop: [Function],

isEmpty: [Function] }

stackProxy.pop();

//=> first

The proxy completely encapsulates the stack’s private state.

And this technique works no matter how we define our implementation object. We could have it mix behaviour in, delegate through a prototype chain, anything we want. Here we’re mixing behaviour into the implementation object’s singleton prototype:

var Person = {

fullName: function () {

return this.firstName + " " + this.lastName;

},

rename: function (first, last) {

this.firstName = first;

this.lastName = last;

return this;

}

};

var HasCareer = {

career: function () {

return this.chosenCareer;

},

setCareer: function (career) {

this.chosenCareer = career;

return this;

}

};

var sam = Object.create(Object.create(null));

extend(Object.getPrototypeOf(sam), Person);

extend(Object.getPrototypeOf(sam), HasCareer);

var samProxy = proxy(sam);

The principle is that instead of forwarding to or mixing in part of the behaviour of our base object, we’re completely defining the implementation object and then creating a proxy for our implementation.

This device is not perfect, for example it is not open for extension as it stands, but it forms the basis for solving more complex problems with encapsulation and coupling.

Encapsulation for Metaobjects

Encapsulation is not just a domain object concern. Lack of encapsulation also affects the relationship between metaobjects. When two or more metaobjects all have access to the same base object via open recursion, they become tightly coupled because they can interact via setting and reading all the base object’s properties. It is impossible to restrict their interaction to a well-defined set of methods.

Encapsulation was a revolutionary idea in the 1980s when Smalltalk launched, and accepted as standard practice in the 1990s when Java rose to prominence. It is now a well-known design imperative. Paradoxically, that means that it often is not necessary to impose strict limitations on objects’ abilities to interact with each other’s internal state, because programmers are much less likely to attempt it today than they were in the 1980s when C was the popular language and manipulating structs was the popular paradigm.

This coupling exists for all metaobject patterns that include open recursion, such as mixins, delegation, and delegation through naive prototypes.

In particular, when chains of naive prototypes form class hierarchies, this coupling leads to the fragile base class problem.

A class hierarchy

A class hierarchy

information

The fragile base class problem is a fundamental architectural problem of object-oriented programming systems where base classes (superclasses) are considered “fragile” because seemingly safe modifications to a base class, when inherited by the derived classes, may cause the derived classes to malfunction. The programmer cannot determine whether a base class change is safe simply by examining in isolation the methods of the base class.–Wikipedia

In JavaScript, prototype chains are vulnerable because changes to one prototype’s behaviour may break another prototype’s behaviour in the same chain.

In this decade, we are much more worried about metaobjects becoming coupled than we are about objects. As we discussed earlier, when introducing the private mixins and forwarding, metaobjects quickly become coupled because of open recursion.

By default, metaobjects like prototypes are tightly coupled to each other because they all manipulate their base object’s properties. And while it is a more difficult problem to solve technically than the problem of coupling peer objects, it is more important to solve it because the problem is more widespread.

Like it or not, many programmers who are perfectly aware that objects should not manipulate each other’s internal state will be surprised to learn that having “classes” manipulate an object’s internal state has exactly the same consequences.

If we can find a way to manage the interface between an object and a metaobject, we can make our programs more robust and discourage others from carelessly introducing coupling.

inner proxies

Our original proxy wrapped around an object, presenting an interface to the “outside world:”

A proxy

A proxy

An object or function can also create its own proxy around an object:

A proxy

A proxy

Here’s one way to use this. Recall our utility for creating a private mixin:

extendPrivately

extendPrivately

Note the highlighted expression. Every method we mix in shares the same new, empty object as its context. This separates them completely from the properties of any object they’re mixed into.

Let’s start with our code for creating proxies:

function proxy (baseObject, optionalPrototype) {

var proxyObject = Object.create(optionalPrototype || null),

methodName;

for (methodName in baseObject) {

if (typeof(baseObject[methodName]) === 'function') {

(function (methodName) {

proxyObject[methodName] = function () {

var result = baseObject[methodName].apply(baseObject, arguments);

return (result === baseObject)

? proxyObject

: result;

}

})(methodName);

}

}

return proxyObject;

}

But now, let’s rewrite the way we do “private” inheritance:

function extendWithProxy (baseObject, behaviour) {

var methodName,

context = proxy(baseObject);

for (methodName in behaviour) {

if (behaviour.hasOwnProperty(methodName)) {

baseObject[methodName] = behaviour[methodName].bind(context);

};

};

return baseObject;

}

Instead of extending an object with methods that only have access to a private object for holding their own state, we’re extending an object with methods that have access to their own private state and to the methods of the object.

For example:

var Person = {

fullName: function () {

return this.firstName + " " + this.lastName;

},

rename: function (first, last) {

this.firstName = first;

this.lastName = last;

return this;

}

};

var HasCareer = {

career: function () {

return this.chosenCareer;

},

setCareer: function (career) {

this.chosenCareer = career;

return this;

},

describe: function () {

return this.fullName() + " is a " + this.chosenCareer;

}

};

var samwise = {};

extendWithProxy(samwise, Person);

extendWithProxy(samwise, HasCareer);

samwise

//=>

{ fullName: [Function],

rename: [Function],

career: [Function],

setCareer: [Function],

describe: [Function] }

Our new object has all the methods of both Person and HasCareer. Let’s try it:

samwise.rename('Sam', 'Wise')

samwise

//=>

{ fullName: [Function],

rename: [Function],

career: [Function],

setCareer: [Function],

describe: [Function] }

samwise.setCareer('Companion');

samwise.describe()

//=> 'Sam Wise is a Companion'

Our describe method has access to HasCareer’s private internal state and to samwise’s fullName method.

an off-topic refinement

If you’re “playing along at home,” you may have noticed this:

samwise.setCareer('Companion');

//=>

{ fullName: [Function],

rename: [Function],

chosenCareer: 'Companion' }

The problem is that the setCareer method returns this, but when extended privately or encapsualtedly (we are making up words), this is the private state of the mixin, not the original object or its proxy.

There are fixes for this. For example:

function extendWithProxy (baseObject, behaviour) {

var methodName,

context = proxy(baseObject);

for (methodName in behaviour) {

if (behaviour.hasOwnProperty(methodName)) {

(function (methodName) {

baseObject[methodName] = function () {

var result = behaviour[methodName].apply(context, arguments);

return (result === context) ? baseObject : result;

};

})(methodName);

};

};

return baseObject;

}

var pepys = {};

extendWithProxy(pepys, Person);

extendWithProxy(pepys, HasCareer);

pepys.rename('Samuel', 'Pepys');

//=>

{ fullName: [Function],

rename: [Function],

career: [Function],

setCareer: [Function],

describe: [Function] }

pepys.setCareer('Diarist');

//=>

{ fullName: [Function],

rename: [Function],

career: [Function],

setCareer: [Function],

describe: [Function] }

pepys.describe();

//=> 'Samuel Pepys is a Diarist'

proxies and prototypes

Let’s try using our extendWithProxy function with a singleton prototype:

var prototype = {};

extendWithProxy(prototype, Person);

extendWithProxy(prototype, HasCareer);

var michael = Object.create(prototype);

michael.rename('Michael', 'Sam');

michael.fullName()

//=> 'Michael Sam'

So far, so good. Now let’s use a shared prototype:

var Careerist = {};

extendWithProxy(Careerist, Person);

extendWithProxy(Careerist, HasCareer);

var michael = Object.create(Careerist),

betwitched = Object.create(Careerist);

michael.rename('Michael', 'Sam');

betwitched.rename('Samantha', 'Stephens');

michael.fullName()

//=> 'Samantha Stephens'

Bzzzzzzzt! This does not work because each of our “encapsulated” mixins hash its own private state, but they are mixed into the shared prototype, so therefore the private state is shared as well.

safekeeping for shared prototypes

This is the same problem we solved with safekeeping for private mixins. Private mixin place the private state in an object shared by the methods in the mixin. But when the methods are mixed into a shared prototype, every object that delegates to those methods is sharing the methods, and this sharing that one shared state.

So we’ll rewrite extendWithProxy to place the proxy in safekeeping:

var number = 0;

function extendWithProxy (baseObject, behaviour) {

var safekeepingName = "__" + ++number + "__",

methodName;

for (methodName in behaviour) {

if (behaviour.hasOwnProperty(methodName)) {

(function (methodName) {

baseObject[methodName] = function () {

var context = this[safekeepingName],

result;

if (context == null) {

context = proxy(this);

Object.defineProperty(this, safekeepingName, {

enumerable: false,

writable: false,

value: context

});

}

result = behaviour[methodName].apply(context, arguments);

return (result === context) ? this : result;

};

})(methodName);

};

};

return baseObject;

}

As with private mixins, we are storing the context in a hidden property within the receiver. However, instead of just being an empty object, the context is now a proxy for the receiver. Thus, every mixed-in method has access to all of the receiver’s methods as well as state shared between the methods being mixed in.

Let’s try it, we’ll create a shared prototype called Careerist, and we’ll mix in HasName and HasCareer. To make sure everything works as expected, we’ll use two different objects delegating to Careerist, and we’ll make sure that both HasName and HasCareer both try to modify the nameproperty.

Unlike previous examples, however, we’ll add a third mixin that relies on the methods from the other two:

var HasName = {

name: function () {

return this.name;

},

setName: function (name) {

this.name = name;

return this;

}

};

var HasCareer = {

career: function () {

return this.name;

},

setCareer: function (name) {

this.name = name;

return this;

}

};

var IsSelfDescribing = {

description: function () {

return this.name() + ' is a ' + this.career();

}

};

var Careerist = {};

extendWithProxy(Careerist, HasName);

extendWithProxy(Careerist, HasCareer);

extendWithProxy(Careerist, IsSelfDescribing);

var michael = Object.create(Careerist),

bewitched = Object.create(Careerist);

michael.setName('Michael Sam');

bewitched.setName('Samantha Stephens');

michael.setCareer('Athlete');

bewitched.setCareer('Thaumaturge');

michael.description()

//=> 'Michael Sam is a Athlete'

bewitched.description()

//=> 'Samantha Stephens is a Thaumaturge'

IsSelfDescribing has its own private state, but it is also able to call .name and .career on the receiver because its private state is also a proxy.

This version of extendWithProxy is a superset of the behaviour of private mixins. It has more moving parts, but mixins that don’t want to call any methods on the receiver needn’t call any methods on the receiver.

encapsulate(...)

In Encapsulation for Metaobjects, we formulated extendWithProxy, a function that would extend an object with some behaviour, and simultaneously encapsulate that behaviour with an inner proxy. It works, but placing two responsibilities in one function is undesirable. The mechanism for encapsulating behaviour should be kept strictly separate from the mechanism for applying behaviour to an object.

Let’s separate the responsibilities. We start with:

function proxy (baseObject, optionalPrototype) {

var proxyObject = Object.create(optionalPrototype || null),

methodName;

for (methodName in baseObject) {

if (typeof(baseObject[methodName]) === 'function') {

(function (methodName) {

proxyObject[methodName] = function () {

var result = baseObject[methodName].apply(baseObject, arguments);

return (result === baseObject)

? proxyObject

: result;

}

})(methodName);

}

}

return proxyObject;

}

var number = 0;

function extendWithProxy (baseObject, behaviour) {

var safekeepingName = "__" + ++number + "__",

methodName;

for (methodName in behaviour) {

if (behaviour.hasOwnProperty(methodName)) {

(function (methodName) {

baseObject[methodName] = function () {

var context = this[safekeepingName],

result;

if (context == null) {

context = proxy(this);

Object.defineProperty(this, safekeepingName, {

enumerable: false,

writable: false,

value: context

});

}

result = methodBody.apply(context, arguments);

return (result === context) ? this : result;

};

})(methodName);

};

};

return baseObject;

}

We can partially apply this function:

var encapsulate = allong.es.callLeft(extendWithProxy, Object.create(null));

Or write the whole thing out explicitly:

var number = 0;

function encapsulate (behaviour) {

var safekeepingName = "__" + ++number + "__",

encapsulatedObject = {};

Object.keys(behaviour).forEach(function (methodName) {

var methodBody = behaviour[methodName];

encapsulatedObject[methodName] = function () {

var context = this[safekeepingName],

result;

if (context == null) {

context = proxy(this);

Object.defineProperty(this, safekeepingName, {

enumerable: false,

writable: false,

value: context

});

}

result = methodBody.apply(context, arguments);

return (result === context) ? this : result;

};

});

return encapsulatedObject;

}

Now we can write:

var Person = encapsulate({

fullName: function () {

return this.firstName + " " + this.lastName;

},

rename: function (first, last) {

this.firstName = first;

this.lastName = last;

return this;

}

});

var HasCareer = encapsulate({

career: function () {

return this.chosenCareer;

},

setCareer: function (career) {

this.chosenCareer = career;

return this;

},

describe: function () {

return this.fullName() + " is a " + this.chosenCareer;

}

});

var samwise = extend({}, Person, HasCareer);

samwise.rename('Sam', 'Wise')

samwise

//=>

{ fullName: [Function],

rename: [Function],

career: [Function],

setCareer: [Function],

describe: [Function] }

samwise.setCareer('Companion');

samwise.describe()

//=> 'Sam Wise is a Companion'

While we’re in the mood to refactor, let’s separate creating a context from getting the current context from building the forwarding methods:

var number = 0;

function encapsulate (behaviour) {

var safekeepingName = "__" + ++number + "__",

encapsulatedObject = {};

function createContext (methodReceiver) {

return proxy(methodReceiver);

}

function getContext (methodReceiver) {

var context = methodReceiver[safekeepingName];

if (context == null) {

context = createContext(methodReceiver);

Object.defineProperty(methodReceiver, safekeepingName, {

enumerable: false,

writable: false,

value: context

});

}

return context;

}

Object.keys(behaviour).forEach(function (methodName) {

var methodBody = behaviour[methodName];

encapsulatedObject[methodName] = function () {

var context = getContext(this),

result = description[methodName].apply(context, arguments);

return (result === context) ? this : result;

};

});

return encapsulatedObject;

}

Encapsulating behaviours is a crucial building block for more sophisticated developments. As we move forward, we will refine encapsulate significantly.

Self

Our encapsulation design uses a proxy to ensure that metaobjects are decoupled from the objects and from each other.

Stripping away most of our implementation, we take something like this:

var hinault = Object.create(null, {

_name: {

value: 'Bernard Hinault',

enumerable: true

},

name: {

value: function () {

return this._name;

},

enumerable: true

}

});

//=>

{ _name: 'Bernard Hinault',

name: [Function] }

And turn it into this:

var lemondContext = {_name: 'Greg Lemond'};

var lemond = Object.create(null, {

_proxy: {

value: proxy,

enumerable: lemondContext

},

name: {

value: function () {

return this._name;

}.bind(lemondContext),

enumerable: true

}

});

//=>

{ name: [Function] }

The second version hides its _name property inside its context object. In our full implementation, context is a proxy for the cyclist object, but this simplified code is sufficient for explaining the shortcoming of this design.

context !== self

Here’s a cycling team:

var laVieClaire = {

_riders: [],

addRider: function (cyclist) {

this._riders.push(cyclist);

return this;

},

riders: function () {

return this._riders;

},

includes: function (cyclist) {

return this._riders.indexOf(cyclist) >= 0;

}

}

It’s pretty obvious what happens if we add our cyclist to a team:

laVieClaire.includes(lemond)

//=> false

laVieClaire.addRider(lemond);

laVieClaire.includes(lemond)

//=> true

Now we’ll make an unencapsulated cyclist that can answer whether it belongs to a team:

var bauer = Object.create(null, {

_name: {

value: 'Steve Bauer',

enumerable: true

},

name: {

value: function () {

return this._name;

},

enumerable: true

},

belongsTo: {

value: function (team) {

return team.includes(this);

},

enumerable: true

}

});

laVieClaire.addRider(bauer);

bauer.belongsTo(laVieClaire)

//=> true

What if we encapsulate this new kind of cyclist?

var andysContext = {_name: 'Andy Hampsten'};

var hampsten = Object.create(null, {

_proxy: {

value: andysContext,

enumerable: false

},

name: {

value: function () {

return this._name;

}.bind(andysContext),

enumerable: true

},

belongsTo: {

value: function (team) {

return team.includes(this);

}.bind(andysContext),

enumerable: true

}

});

laVieClaire.addRider(hampsten);

laVieClaire.includes(hampsten)

//=> true

hampsten.belongsTo(laVieClaire)

//=> false

What happened? Well, let’s look closely at belongsTo’s function:

function (team) {

return team.includes(this);

}.bind(andysContext)

The answer is clear: Like all encapsulated methods, we’re binding it to andysContext so that this always refers to its private data (andysContext) rather than the object (hampsten). The variable this is always bound to the current context, so when we call team.includes(this), we’re asking if the team includes the context, not the cyclist object.

Bzzzt!

The trouble is that this has two jobs in every function: It’s the context, and by default, it’s also the receiver of the method invocation. JavaScript does not separate these two ideas: If you bind the context of a method, you lose the ability to refer to the receiver of the method.

separating context from selfhood

Other languages do not have this problem, because the concept of an object’s private state and its “selfhood” are separate. With a ruby instance method, self is a variable bound to the receiver of the message, and the state of the object is held in “instance variables,” variables whose names are prefixed with @:

class Example

def setFoo (value)

@foo = value

end

end

myExample = Example.new

myExample.setFoo(42)

myExample.foo

#=> NoMethodError: undefined method `foo'

There is no way to access an instance variable through an object: In Ruby, myExample.foo is an attempt to access a method called foo, not the instance variable @foo.

While Ruby does not have this, it has a similar variable, self. But you can’t use self to access instance variables within a method, calling self.foo is also attempting to invoke a method:

class AnotherExample

def setFoo (value)

@foo = value

end

def getFoo

self.foo

end

end

secondExample = AnotherExample.new

secondExample.setFoo(42)

secondExample.getFoo()

#=> NoMethodError: undefined method `foo'

Ruby thus cleanly separates context (in the form of private instance variables) from selfhood (the self variable). If we want to have private, hidden context and selfhood, we’ll also need to separate the ideas.

One simple method is to copy what Ruby does, sort of. this.foo is a little like @foo,and this.self is a little like self. This implies, correctly, that you cannot have your own property named self.

Here’s the createContext function from encapsulate, modified to support self:

function createContext (methodReceiver) {

return Object.defineProperty(

proxy(methodReceiver),

'self',

{ writable: false, enumerable: false, value: methodReceiver }

);

}

We can use the revamped encapsulate:

var laVieClaire = {

_riders: [],

addRider: function (cyclist) {

this._riders.push(cyclist);

return this;

},

riders: function () {

return this._riders;

},

includes: function (cyclist) {

return this._riders.indexOf(cyclist) >= 0;

}

}

var jeanFrancoisBernard = encapsulate({

belongsTo: function (team) {

return team.includes(this.self);

}

});

laVieClaire.addRider(jeanFrancoisBernard);

jeanFrancoisBernard.belongsTo(laVieClaire)

//=> true

warning

The peril of enabling this.self in JavaScript is that encapsulated methods now have a “back door” into directly modifying the base object. When we get into composing metaobjects, we’ll work very hard to keep metaobjects from invoking each other’s methods in an unstructured way. this.self could be used to subvert such schemes.

Privacy

Our encapsulate function sets up a sharp demarcation: An encapsulated metaobject has one or more methods that are visible to other objects. Those “public” methods can update and query properties, but the properties are hidden from the public. The properties are “private.”

This scheme is perfectly cromulent for metaobjects that represent small, focused and simple responsibilities. But what if we have a metaobject with complex internal structure and workings?

We may, for example, wish to take a code fragment and group it together, giving it a name and making it obvious that it represents a single responsibility. We could turn this:27

var Account = encapsulate({

getOutstanding: function () {

// ...elided...

},

printOwing: function () {

this.printBanner();

//print details

console.log("name: " + this._name);

console.log("amount " + this.getOutstanding());

return this;

}

// ...elided...

});

Into this:

var Account = encapsulate({

getOutstanding: function () {

// ...elided...

},

printOwing: function () {

this.printBanner();

this._printDetails();

return this;

},

_printDetails: function () {

console.log("name: " + this._name);

console.log("amount " + this.getOutstanding());

return this;

}

// ...elided...

});

This works, however _printDetails is public: Even though we use a common naming convention to remind ourselves not to use it externally, we have exposed ourselves to the possibility that in a moment of haste or ignorance, another object may invoke the method.

This increases the surface area of our Account metaobject by exposing another method. Furthermore, exposing a method that printOwing itself calls means that its dependents include all of the objects that have method that invoke printDetails directly or invoke it indirectly by invokingprintOwning.

_printDetails is, thankfully, a query. Consider this similar example featuring a helper method that performs an update:

var Account = encapsulate({

credit: function (amount) {

// ...elided...

},

debit: function (amount) {

// ...elided...

},

processCheque: function (cheque) {

// ...elided...

_transferTo(cheque.depositAccount, cheque.amount);

return this;

},

_transferTo: function (otherAccount, amount) {

this.credit(amount);

otherAccount.debit(amount);

return this;

}

// ...elided...

});

_transferTo is also public, and affects the internal state of an account. Therefore, exposing it to the world immediately couples every piece of code that invokes it with every piece of code that depends upon the state of an account’s balance.

The ideal situation is to avoid making such “helper methods” public. For example, we can take advantage of JavaScript’s closures and convert the methods into helper functions:

function _transferTo (fromAccount, toAccount, amount) {

fromAccount.credit(amount);

toAccount.debit(amount);

return fromAccount;

}

var Account = encapsulate({

credit: function (amount) {

// ...elided...

},

debit: function (amount) {

// ...elided...

},

processCheque: function (cheque) {

// ...elided...

_transferTo(this, cheque.depositAccount, cheque.amount);

return this;

}

// ...elided...

});

This one works very well because it doesn’t actually use any of an account’s internal private state. But it wouldn’t work if it needed to mutate the balance directly,a s the private state is encapsulated.

What if we want to make a method that has access to the encapsulated metaobject’s private state but is not visible outside of the metaobject?

Let’s review the architecture. An account looks like this:

AccountEncapulation

AccountEncapulation

The encapsulate function structures each object so that there is an unenumerated, “hidden” object representing private state, and each method executes in that object’s context.

Now we want to have a method that is hidden from the public, but available to each method. Well, the answer writes itself when you ask the question the right way:

Account Private Method

Account Private Method

If we want a private method that is available only to our public methods, we should place it in the object we already have that holds private state for our public methods.

private methods

Recall our encapsulate method:

function proxy (baseObject, optionalPrototype) {

var proxyObject = Object.create(optionalPrototype || null),

methodName;

for (methodName in baseObject) {

if (typeof(baseObject[methodName]) === 'function') {

(function (methodName) {

proxyObject[methodName] = function () {

var result = baseObject[methodName].apply(baseObject, arguments);

return (result === baseObject)

? proxyObject

: result;

}

})(methodName);

}

}

return proxyObject;

}

var number = 0;

function encapsulate (behaviour) {

var safekeepingName = "__" + ++number + "__",

encapsulatedObject = {};

function createContext (methodReceiver) {

return Object.defineProperty(

proxy(methodReceiver),

'self',

{ writable: false, enumerable: false, value: methodReceiver }

);

}

function getContext (methodReceiver) {

var context = methodReceiver[safekeepingName];

if (context == null) {

context = createContext(methodReceiver);

Object.defineProperty(methodReceiver, safekeepingName, {

enumerable: false,

writable: false,

value: context

});

}

return context;

}

Object.keys(behaviour).forEach(function (methodName) {

var methodBody = behaviour[methodName];

encapsulatedObject[methodName] = function () {

var context = getContext(this),

result = description[methodName].apply(context, arguments);

return (result === context) ? this : result;

};

});

return encapsulatedObject;

}

Note that our proxy function takes an optionalPrototype. Now we can use it, with a little fiddling to scan the method names for private methods:

function proxy (baseObject, optionalPrototype) {

var proxyObject = Object.create(optionalPrototype || null),

methodName;

for (methodName in baseObject) {

if (typeof(baseObject[methodName]) === 'function') {

(function (methodName) {

proxyObject[methodName] = function () {

var result = baseObject[methodName].apply(baseObject, arguments);

return (result === baseObject)

? proxyObject

: result;

}

})(methodName);

}

}

return proxyObject;

}

var number = 0;

function encapsulate (behaviour) {

var safekeepingName = "__" + ++number + "__",

methods = Object.keys(behaviour).filter(function (methodName) {

return typeof behaviour[methodName] === 'function';

}),

privateMethods = methods.filter(function (methodName) {

return methodName[0] === '_';

}),

publicMethods = methods.filter(function (methodName) {

return methodName[0] !== '_';

});

function createContext (methodReceiver) {

var innerProxy = proxy(methodReceiver);

privateMethods.forEach(function (methodName) {

innerProxy[methodName] = behaviour[methodName];

});

return Object.defineProperty(

innerProxy,

'self',

{ writable: false, enumerable: false, value: methodReceiver }

);

}

function getContext (methodReceiver) {

var context = methodReceiver[safekeepingName];

if (context == null) {

context = createContext(methodReceiver);

Object.defineProperty(methodReceiver, safekeepingName, {

enumerable: false,

writable: false,

value: context

});

}

return context;

}

return publicMethods.reduce(function (acc, methodName) {

var methodBody = behaviour[methodName];

acc[methodName] = function () {

var context = getContext(this),

result = behaviour[methodName].apply(context, arguments);

return (result === context) ? this : result;

};

return acc;

}, {});

}

var MultiTalented = encapsulate({

_englishList: function (list) {

var butLast = list.slice(0, list.length - 1),

last = list[list.length - 1];

return butLast.length > 0

? [butLast.join(', '), last].join(' and ')

: last;

},

constructor: function () {

this._careers = [];

return this;

},

addCareer: function (career) {

this._careers.push(career);

return this;

},

careers: function () {

return this._englishList(this._careers);

}

});

var nilsson = Object.create(MultiTalented).constructor();

nilsson.addCareer('Singer').addCareer('Songwriter').careers()

//=> 'Singer and Songwriter'

nilsson._englishList

//=> undefined

Our new architecture looks like this:

Private method using a prototype for the proxy

Private method using a prototype for the proxy

When careers invokes this._englishList, the context is our proxy, shown as property __12345__. There is no implementation of _englishList in the proxy itself, but there is in the prototype we construct for it, so invoking it as a method works for any of the public (or other private) methods we define in this behaviour.

There are many other ways to implement encapsulation with public and private methods. There are especially many other ways to distinguish public from private methods: Naming conventions are definitely a question of taste.

The thing to remember is that the specific design and implementation is not the point, it’s a vehicle for thinking about access control, coupling, design, and the interaction between objects and metaobjects in a software system.

“It is like a finger, pointing away to the moon. Don’t concentrate on the finger, or you will miss all that heavenly glory.”—Bruce Lee, recounting a teaching from Buddhism’s Lankavatara Sutra.

Closing Encapsulated Objects

There are two things we’ve ignored so far. Looking back at our introduction to Metaobjects, we discussed whether metaobjects had forwarding or delegation semantics. We also discussed whether metaobjects were open or closed for extension and we discussed whether the target for forwarding/delegation was early- or late-bound.

closed for modification and extension

Our encapsulate function is closed for modification. Let’s take another look at it:

var number = 0;

function encapsulate (behaviour) {

var safekeepingName = "__" + ++number + "__",

properties = Object.keys(behaviour),

methods = properties.filter(function (methodName) {

return typeof behaviour[methodName] === 'function';

}),

privateMethods = methods.filter(function (methodName) {

return methodName[0] === '_';

}),

publicMethods = methods.filter(function (methodName) {

return methodName[0] !== '_';

}),

definedMethods = methodsOfType(behaviour, publicMethods, 'function'),

dependencies = methodsOfType(behaviour, properties, 'undefined'),

encapsulatedObject = {};

function createContext (methodReceiver) {

var innerProxy = proxy(methodReceiver);

privateMethods.forEach(function (methodName) {

innerProxy[methodName] = behaviour[methodName];

});

return Object.defineProperty(

innerProxy,

'self',

{ writable: false, enumerable: false, value: methodReceiver }

);

}

function getContext (methodReceiver) {

var context = methodReceiver[safekeepingName];

if (context == null) {

context = createContext(methodReceiver);

Object.defineProperty(methodReceiver, safekeepingName, {

enumerable: false,

writable: false,

value: context

});

}

return context;

}

definedMethods.forEach(function (methodName) {

var methodBody = behaviour[methodName];

encapsulatedObject[methodName] = function () {

var context = getContext(this),

result = methodBody.apply(context, arguments);

return (result === context) ? this : result;

};

});

dependencies.forEach(function (methodName) {

if (encapsulatedObject[methodName] == null) {

encapsulatedObject[methodName] = void 0;

}

});

return encapsulatedObject;

}

When we use encapsulate to create a metaobject, we associate each of the methods with a partial proxy for this:

Songwriter

Songwriter

Once the metaobject has been created, there is (deliberately) no obvious way for any other object to access its partial proxy. For this reason, it is difficult to extend a metaobject created by encapsulate. If we try to replace an existing method, the new version will not have access to the proxy.

Here’s a (slightly changed) version of Songwriter that completely hides its implementation of a songlist:

var Songwriter = encapsulate({

constructor: function () {

this._songs = [];

return this;

},

addSong: function (name) {

this._songs.push(name);

return this;

},

songlist: function () {

return this._songs.join(', ');

}

});

We could try to modify it to produce a more readable songlist:

function englishList (list) {

var butLast = list.slice(0, list.length - 1),

last = list[list.length - 1];

return butLast.length > 0

? [butLast.join(', '), last].join(' and ')

: last;

}

Songwriter.songlist = function () {

return englishList(this._songs);

}

This won’t work, the replacement method will not access the partial proxy, but instead its context will be this directly:

Songwriter, extended

Songwriter, extended

This is not what we want! Open recursion (unrestricted access to this) re-introduces the coupling we worked so hard to avoid. And, the method won’t work because _songs is a property of the partial proxy, not of the underlying this.

The same is true if we tried to add a new method. Encapsulated behaviours are closed for extension as well as being closed for extension.

This is terrible if we like the free-wheeling “monkey-patching” style of metaprogramming popular in other language communities, but our code is actually doing its job properly by enforcing clearly defined limits on how disparate pieces of code can influence each other’s behaviour.

early warning systems

“Everybody Lies”—Dr. Gregory House

The one drawback of our code is that it lies: You can add or change a method, but it doesn’t really work. It would be far better to catch these incorrect practices earlier.

We can prevent methods from being replaced using Object.defineProperty and prevent new methods from being added with Object.preventExtensions:

var number = 0;

function encapsulate (behaviour) {

var safekeepingName = "__" + ++number + "__",

properties = Object.keys(behaviour),

methods = properties.filter(function (methodName) {

return typeof behaviour[methodName] === 'function';

}),

privateMethods = methods.filter(function (methodName) {

return methodName[0] === '_';

}),

publicMethods = methods.filter(function (methodName) {

return methodName[0] !== '_';

}),

definedMethods = methodsOfType(behaviour, publicMethods, 'function'),

dependencies = methodsOfType(behaviour, properties, 'undefined'),

encapsulatedObject = {};

function createContext (methodReceiver) {

var innerProxy = proxy(methodReceiver);

privateMethods.forEach(function (methodName) {

innerProxy[methodName] = behaviour[methodName];

});

return Object.defineProperty(

innerProxy,

'self',

{ writable: false, enumerable: false, value: methodReceiver }

);

}

function getContext (methodReceiver) {

var context = methodReceiver[safekeepingName];

if (context == null) {

context = createContext(methodReceiver);

Object.defineProperty(methodReceiver, safekeepingName, {

enumerable: false,

writable: false,

value: context

});

}

return context;

}

definedMethods.forEach(function (methodName) {

var methodBody = behaviour[methodName];

Object.defineProperty(encapsulatedObject, methodName, {

enumerable:: true,

writable: false,

value: function () {

var context = getContext(this),

result = methodBody.apply(context, arguments);

return (result === context) ? this : result;

}

});

});

dependencies.forEach(function (methodName) {

if (encapsulatedObject[methodName] == null) {

encapsulatedObject[methodName] = void 0;

}

});

return Object.preventExtensions(encapsulatedObject);

}

Now if we try to modify Songwriter, it won’t work. If we’re in “strict mode,” we get an error:

var Songwriter = encapsulate({

constructor: function () {

this._songs = [];

return this;

},

addSong: function (name) {

this._songs.push(name);

return this;

},

songlist: function () {

return this._songs.join(', ');

}

});

!function () {

"use strict"

Songwriter.songlist = function () {

return englishList(this._songs)

};

}();

//=> TypeError: Cannot assign to read only property 'songlist' of #<Object>

This forces us to build new functionality with composeMetaobjects and using the tools we’ve designed to control coupling. That may seem like it is taking away our control, like we are pandering to the “lowest common denominator” of programmer while hobbling the good programmers, but this is not the case:

We aren’t removing a powerful and useful feature because it is “difficult to read,” we’re removing a feature that introduces coupling as part and parcel of adding a feature (composeability for metaobjects) that provides an even-more-powerful set of capabilities.

Decoupling with Partial Proxies

Some objects have multiple responsibilities. A Person can be an Author, can HaveChildren, can HaveInvestments and so forth. Each particular responsibility can be cleanly separated into its own metaobject, and their state combined into one object with techniques like our private behaviours that work with shared prototypes using safekeeping for shared prototypes.

This cleanly separates the code we write along lines of responsibility. Encapsulating the base object within a proxy reduces the surface area available for coupling by hiding all private state. But each behaviour has access to all of the object’s methods, and every responsibility we add swells this set of methods, increasing the surface area again.

We saw in Encapsulation for Metaobjects that we can use proxies to prevent metaobjects from manipulating the base object’s properties. Now we’re going to explicitly limit the methods that each metaobject can access on the base object. This lowers the coupling between metaobjects.

Let’s start by recalling a basic version of the encapsulate function:28

var number = 0;

function encapsulate (behaviour) {

var safekeepingName = "__" + ++number + "__",

encapsulatedObject = {};

function createContext (methodReceiver) {

return proxy(methodReceiver);

}

function getContext (methodReceiver) {

var context = methodReceiver[safekeepingName];

if (context == null) {

context = createContext(methodReceiver);

Object.defineProperty(methodReceiver, safekeepingName, {

enumerable: false,

writable: false,

value: context

});

}

return context;

}

Object.keys(behaviour).forEach(function (methodName) {

var methodBody = behaviour[methodName];

encapsulatedObject[methodName] = function () {

var context = getContext(this),

result = description[methodName].apply(context, arguments);

return (result === context) ? this : result;

};

});

return encapsulatedObject;

}

The key line of code is context = proxy(methodReceiver);. This lazily attaches a proxy of the method receiver to a “hidden” property. Thereafter, every method of that behaviour invoked on the same receiver will use the same context.

Thus, the functions of a behaviour do not have access to the receiver’s properties. In addition, since each behaviour gets its own proxy, behaviours cannot access each other’s properties either.

However, proxy exposes all of the receiver’s methods to every behaviour, including methods that other behaviours add. This means that behaviours can invoke each other’s methods and become interdependent.

This is almost never what we want. If we’re trying to decouple behaviours from each other, we want to strictly limit the methods exposed to each behaviour. Typically, there are two cases. First, a behaviour may provide completely independent behaviour: It does not rely on the base object’s properties or methods.

In these examples, HasName and HasCareer are self-contained behaviours. They update and queries its own private state, and do not invoke any other methods. Both use a property called value, but if they re both mixed into the same base object, they’ll each get their own property and this each have their own value property:

var HasName = encapsulate({

name: function () {

return this.name;

},

setName: function (name) {

this.name = name;

return this;

}

});

var HasCareer = encapsulate({

career: function () {

return this.name;

},

setCareer: function (name) {

this.name = name;

return this;

}

});

In contrast, IsSelfDescribing is a behaviour that depends on the receiver implementing both the name() and career() methods. We know this from inspecting the code, and fortunately it is a small and simple behaviour:

var IsSelfDescribing = encapsulate({

description: function () {

return this.name() + ' is a ' + this.career();

}

});

If we program in this style, writing behaviours that have dependencies hidden inside the code, we will introduce a lot of coupling, with behaviours becoming dependent upon each other in an unstructured way.

managing dependencies

Behaviours–like IsSelfDescribing–that depend upon the base object or another behaviour implementing methods are said to stack upon other behaviours.

IsSelfDescribing stacked on HasName and HasCareer

IsSelfDescribing stacked on HasName and HasCareer

We can manage these dependencies by making dependencies explicit instead of implicit. First, we’ll express the dependencies using a simple convention: If a behaviour depends upon a method, it must bind undefined to the name of the method.

This is how we’ll express this for IsSelfDescribing:

var IsSelfDescribing = encapsulate({

name: undefined,

career: undefined,

description: function () {

return this.name() + ' is a ' + this.career();

}

});

The way we’ll enforce this requirement is that we will no longer provide every behaviour with a proxy for all of the object’s methods. Instead, we’ll only expose the methods that the behaviour defines publicly or explicitly requires.

To do that, we’ll write the function partialProxy. It works like proxy, but instead of iterating over the base object’s properties, it iterates over a subset we provide. We will also rewrite extendsWithProxy to use partialProxy and the declared dependencies:

function partialProxy (baseObject, methods) {

var proxyObject = Object.create(null);

methods.forEach(function (methodName) {

proxyObject[methodName] = function () {

var result = baseObject[methodName].apply(baseObject, arguments);

return (result === baseObject)

? proxyObject

: result;

}

});

return proxyObject;

}

var number = 0;

function methodsOfType (behaviour, type) {

var methods = [],

methodName;

for (methodName in behaviour) {

if (typeof(behaviour[methodName]) === type) {

methods.push(methodName);

}

};

return methods;

}

function encapsulate (behaviour) {

var safekeepingName = "__" + ++number + "__",

definedMethods = methodsOfType(behaviour, 'function'),

dependencies = methodsOfType(behaviour, 'undefined'),

encapsulatedObject = {};

function createContext (methodReceiver) {

return partialProxy(methodReceiver, dependencies);

}

function getContext (methodReceiver) {

var context = methodReceiver[safekeepingName];

if (context == null) {

context = createContext(methodReceiver);

Object.defineProperty(methodReceiver, safekeepingName, {

enumerable: false,

writable: false,

value: context

});

}

return context;

}

definedMethods.forEach(function (methodName) {

var methodBody = behaviour[methodName];

encapsulatedObject[methodName] = function () {

var context = getContext(this),

result = methodBody.apply(context, arguments);

return (result === context) ? this : result;

};

});

return encapsulatedObject;

}

Our createContext function now calls partialProxy instead of proxy. We can try it with HasName, HasCareer, and IsSelfDescribing:

var Careerist = extend({}, HasName, HasCareer, IsSelfDescribing);

var michael = Object.create(Careerist),

bewitched = Object.create(Careerist);

michael.setName('Michael Sam');

bewitched.setName('Samantha Stephens');

michael.setCareer('Athlete');

bewitched.setCareer('Thaumaturge');

michael.description()

//=> 'Michael Sam is a Athlete'

bewitched.description()

//=> 'Samantha Stephens is a Thaumaturge'

We have the same behaviour as before, but we’ve required that behaviours explicitly declare the methods the depend on. This prevents coupling from sneaking up on us accidentally, and it makes it easier to see the coupling when we’re reviewing the code.

why this matters

To summarize, instead of every behaviour having access to all of the receiver’s methods, each behaviour only has access to methods it explicitly depends on. This helps by documenting each behaviour’s dependencies and by eliminating accidental coupling between behaviours.

Composing Metaobjects

Up to now, we’ve combined behaviours in an object (such as to create a prototype) using extend to forcibly copy the methods from our behaviours into an empty object. The core of extend is this block:

for (key in provider) {

if (Object.prototype.hasOwnProperty.call(provider, key)) {

consumer[key] = provider[key];

};

};

We evaluate consumer[key] = provider[key]; for each method in our behaviour. This has the desired effect when we’re composing two behaviours that have a disjoint set of methods. But if they both have a method with the same name, one will overwrite the other.

This is almost never the right thing to do. If you think about composition, what we want is that if we have a behaviour A and a behaviour B, we want the behaviour AB to be both A and B. If we have some expectation about the behaviour of A, we should have the same expectation of AB.

If A and B both define a method, m(), then calling AB.m() should be equivalent to calling A.m(). This is not the case if B.m overwrites A.m. Likewise AB.m() should also be equivalent to calling B.m(). We want AB.m() to be equivalent to both A.m() and B.m().

For example:

var SingsSongs = encapsulate({

_songs: null,

constructor: function () {

this._songs = [];

return this;

},

addSong: function (name) {

this._songs.push(name);

return this;

},

songs: function () {

return this._songs;

}

});

var HasAwards = encapsulate({

_awards: null,

constructor: function () {

this._awards = [];

return this;

},

addAward: function (name) {

this._awards.push(name);

return this;

},

awards: function () {

return this._awards;

}

});

var AwardWinningSongwriter = extend(Object.create(null), SingsSongs, HasAwards),

tracy = Object.create(AwardWinningSongwriter).constructor();

tracy.songs()

//=> undefined

HasAwards.constructor has overwritten SingsSongs.constructor, exactly what we said it should not do.

Composeable Espresso Cups

Composeable Espresso Cups

composing methods

We know a little something about composing methods from Method Objects. One way to compose two methods is to evaluate one and then the other. Here’s what a very simple version looks like for functions. It’s a protocol:

function simpleProtocol (fn1, fn2) {

return function composed () {

fn1.apply(this, arguments);

return fn2.apply(this, arguments);

}

}

Using this formulation, we can say that given A.m and B.m, then AB.m = simpleProtocol(A.m, B.m).

We can promote this idea to composing metaobjects. First, we generalize simpleProtocol to handle an number of functions and the degenerate case of one function. We’ll call our generalization orderProtocol.

We can then write composeMetaobjects to apply this to encapsulated methods:

var __slice = [].slice;

function isUndefined (value) {

return typeof value === 'undefined';

}

function isntUndefined (value) {

return typeof value !== 'undefined';

}

function isFunction (value) {

return typeof value === 'function';

}

function orderProtocol () {

if (arguments.length === 1){

return arguments[0];

}

else {

var fns = arguments;

return function composed () {

for (var i = 0; i < (fns.length - 1); ++i) {

fns[i].apply(this, arguments);

}

return fns[fns.length - 1].apply(this, arguments);

}

}

}

function propertiesToArrays (metaobjects) {

return metaobjects.reduce(function (collected, metaobject) {

var key;

for (key in metaobject) {

if (key in collected) {

collected[key].push(metaobject[key]);

}

else collected[key] = [metaobject[key]]

}

return collected;

}, {})

}

function resolveUndefineds (collected) {

return Object.keys(collected).reduce(function (resolved, key) {

var values = collected[key];

if (values.every(isUndefined)) {

resolved[key] = undefined;

}

else resolved[key] = values.filter(isntUndefined);

return resolved;

}, {});

}

function applyProtocol(resolveds, protocol) {

return Object.keys(resolveds).reduce( function (applied, key) {

var value = resolveds[key];

if (isUndefined(value)) {

applied[key] = value;

}

else if (value.every(isFunction)) {

applied[key] = protocol.apply(null, value);

}

else throw "Don't know what to do with " + value;

return applied;

}, {});

}

function composeMetaobjects {

var metaobjects = __slice.call(arguments, 0),

arrays = propertiesToArrays(metaobjects),

resolved = resolveUndefineds(arrays),

composed = applyProtocol(resolved, orderProtocol);

return composed;

}

Let’s revisit our example from above, but this time we’ll composeMetaobjects our metaobjects instead of extending an object:

var SingsSongs = encapsulate({

_songs: null,

constructor: function () {

this._songs = [];

return this;

},

addSong: function (name) {

this._songs.push(name);

return this;

},

songs: function () {

return this._songs;

}

});

var HasAwards = encapsulate({

_awards: null,

constructor: function () {

this._awards = [];

return this;

},

addAward: function (name) {

this._awards.push(name);

return this;

},

awards: function () {

return this._awards;

}

});

var AwardWinningSongwriter = composeMetaobjects(SingsSongs, HasAwards),

tracy = Object.create(AwardWinningSongwriter).constructor();

tracy.addSong('Fast Car');

tracy.songs()

//=> [ 'Fast Car' ]

Now AwardWinningSongwriter.constructor does exactly what we want it to do.

return value protocols

Our orderProtocol is very simple. But there are some cases it doesn’t handle well. Sometimes we want to decorate a method with some behaviour, but not take over the return value.

This often happens when we have two responsibilities, but they are not “peers:” One is a primary responsibility, while the other is a secondary and lesser responsibility.

For example, a Songwriter’s responsibility is to manage its songs:

var Songwriter = encapsulate({

constructor: function () {

this._songs = [];

return this.self;

},

addSong: function (name) {

this._songs.push(name);

return this.self;

},

songs: function () {

return this._songs;

}

});

Secondary responsibilities are often orthogonal concerns. Waving aside ECMAScript-6’s Object.observe for a moment, we could design a program that has views that are notified when models that delegate to Songwriter are changed. We would start with the basic idea that something like a view could “subscribe” to a model that is a Subscribable:

var Subscribable = encapsulate({

constructor: function () {

this._subscribers = [];

return this.self;

},

subscribe: function (callback) {

this._subscribers.push(callback);

},

unsubscribe: function (callback) {

this._subscribers = this._subscribers.filter( function (subscriber) {

return subscriber !== callback;

});

},

subscribers: function () {

return this._subscribers;

},

notify: function () {

receiver = this;

this._subscribers.forEach( function (subscriber) {

subscriber.apply(receiver.self, arguments);

});

}

});

A SubscribableSongwriter would obviously be composeMetaobjects(Songwriter, Subscribable). But in order for subscribing to be useful, we want notify() to be called whenever we call addSong(). We can do that by adding more behaviour to addSong:

var SubscribableSongwriter = composeMetaobjects(

Songwriter,

Subscribable,

encapsulate({

notify: undefined,

addSong: function () { this.notify(); }

})

);

var sweetBabyJames = Object.create(SubscribableSongwriter).constructor();

The third metaobject, encapsulate({notify: undefined, addSong: function () { this.notify(); }}), appends a behaviour to addSong that calls notify() when a song is added. We can see that it works using this primitive “view:”

var SongwriterView = {

constructor: function (model, name) {

this.model = model;

this.name = name;

this.model.subscribe(this.render.bind(this));

return this;

},

_englishList: function (list) {

var butLast = list.slice(0, list.length - 1),

last = list[list.length - 1];

return butLast.length > 0

? [butLast.join(', '), last].join(' and ')

: last;

},

render: function () {

var songList = this.model.songs().length > 0

? [" has written " + this._englishList(this.model.songs().map\

(function (song) {

return "'" + song + "'"; }))]

: [];

console.log(this.name + songList);

return this;

}

};

var jamesView = Object.create(SongwriterView).constructor(sweetBabyJames, 'James \

Taylor');

sweetBabyJames.addSong('Fire and Rain')

//=> James Taylor has written 'Fire and Rain'

undefined

Or did it work? Let’s look at Songwriter.addSong:

function (name) {

this._songs.push(name);

return this.self;

}

It is supposed to return this.self, but it actually returns undefined, because it is returning whatever the last addSong behavior returns. The last behaviour is function () { this.notify(); }, and that doesn’t return anything at all, so we get undefined.

This is almost never what we want. We could “fix” function () { this.notify(); } to return this.self, but the larger issue is that function () { this.notify(); } is a secondary responsibility. It shouldn’t know what is to be returned.

What we want is to be able to compose behaviour, but make responsibility for the return value optional. We’ll do that with a simple convention: Not returning a value, or returning undefined, advertises that a function does not have responsibility for the return value.

We can implement this with a simple change to our ordering strategy:

var __slice = [].slice;

function isntUndefined (value) {

return typeof value !== 'undefined';

}

function orderProtocol2 () {

if (arguments.length === 1) {

return arguments[0];

}

else {

var fns = __slice.call(arguments, 0);

return function composed () {

var args = arguments,

context = this,

values = fns.map(function (fn) {

return fn.apply(context, args);

}).filter(isntUndefined);

if (values.length > 0) {

return values[values.length - 1];

}

}

}

}

function composeMetaobjects {

var metaobjects = __slice.call(arguments, 0),

arrays = propertiesToArrays(metaobjects),

resolved = resolveUndefineds(arrays),

composed = applyProtocol(resolved, orderProtocol2);

return composed;

}

var CorrectSubscribableSongwriter = composeMetaobjects(

Songwriter,

Subscribable,

encapsulate({

notify: undefined,

addSong: function () { this.notify(); }

})

);

var sweetBabyJames = Object.create(CorrectSubscribableSongwriter).constructor();

var jamesView = Object.create(SongwriterView).constructor(sweetBabyJames, 'James \

Taylor');

sweetBabyJames.addSong('Fire and Rain') === sweetBabyJames

//=> James Taylor has written 'Fire and Rain'

true

Success! It now returns the original value, the receiver, as we intended.

information

One consequence of returning the last non-undefined value is that it is possible to want to return undefined but to have that value usurped by another behaviour that returns something other than undefined.

In practice, this is rare, but if it does arise it will be necessary to work around this limitation or to change strategies.

specialization and prototype chains

Our scheme for decorating methods with additional behaviour like addSong: function () { this.notify(); } works perfectly when we’re composing behaviour. This works in concert with prototypes when we’re building a single prototype.

What this scheme can’t do is compose behaviour across prototypes. Although we prefer to avoid creating elaborate prototype chains, let’s presume that we have an excellent reason for wanting to create an Songwriter prototype, and also create a SubscribableSongwriter prototype that delegates to Songwriter.

In other words:

A small prototype chain

A small prototype chain

Now we want to make a SubscribableSongwriter that delegates to Songwriter. There is a trick for making behaviour that delegates to a prototype: We compose the behaviour we want with an object that delegates to the desired prototype. In effect, we’re composing the delegation with the behaviour we want.

We’ll need to adjust applyProtocol to start with a seed that can be passed in with a prototype correctly set:

function applyProtocol(seed, resolveds, protocol) {

return Object.keys(resolveds).reduce( function (applied, key) {

var value = resolveds[key];

if (isUndefined(value)) {

applied[key] = value;

}

else if (value.every(isFunction)) {

applied[key] = protocol.apply(null, value);

}

else throw "Don't know what to do with " + value;

return applied;

}, seed);

}

We’ll also have to change composeMetaobjects to generate the correct seed (and while we’re at it, check that everything being composed have compatible prototypes):

function canBeMergedInto (object1, object2) {

var prototype1 = Object.getPrototypeOf(object1),

prototype2 = Object.getPrototypeOf(object2);

if (prototype1 === null) return prototype2 === null;

if (prototype2 === null) return true;

if (prototype1 === prototype2) return true;

return Object.prototype.isPrototypeOf.call(prototype2, prototype1);

}

// shim if allong.es.callLeft not available

var callLeft2 = (function () {

if (typeof callLeft == 'function') {

return callLeft;

}

else if (typeof allong === 'object' && typeof allong.es === 'object' && typeof \

allong.es.callLeft === 'function') {

return allong.es.callLeft;

}

else {

return function callLeft2 (fn, arg2) {

return function callLeft2ed (arg1) {

return fn.call(this, arg1, arg2);

};

};

}

})();

function seedFor (objectList) {

var seed = objectList[0] == null

? Object.create(null)

: Object.create(Object.getPrototypeOf(objectList[0])),

isCompatibleWithSeed = callLeft2(canBeMergedInto, seed);

if (!objectList.every(isCompatibleWithSeed)) throw 'incompatible prototypes';

return seed;

}

function composeMetaobjects {

var metaobjects = __slice.call(arguments, 0),

arrays = propertiesToArrays(metaobjects),

resolved = resolveUndefineds(arrays),

seed = seedFor(metaobjects),

composed = applyProtocol(seed, resolved, orderProtocol2);

return composed;

}

We have what we need to build our object:

var Songwriter = encapsulate({

constructor: function () {

this._songs = [];

return this.self;

},

addSong: function (name) {

this._songs.push(name);

return this.self;

},

songs: function () {

return this._songs;

}

});

var Subscribable = encapsulate({

constructor: function () {

this._subscribers = [];

return this.self;

},

subscribe: function (callback) {

this._subscribers.push(callback);

},

unsubscribe: function (callback) {

this._subscribers = this._subscribers.filter( function (subscriber) {

return subscriber !== callback;

});

},

subscribers: function () {

return this._subscribers;

},

notify: function () {

receiver = this;

this._subscribers.forEach( function (subscriber) {

subscriber.apply(receiver.self, arguments);

});

}

});

var SubscribableSongwriter = composeMetaobjects(

Object.create(Songwriter),

Subscribable,

encapsulate({

notify: undefined,

addSong: function () { this.notify(); }

})

);

var SongwriterView = {

constructor: function (model, name) {

this.model = model;

this.name = name;

this.model.subscribe(this.render.bind(this));

return this;

},

_englishList: function (list) {

var butLast = list.slice(0, list.length - 1),

last = list[list.length - 1];

return butLast.length > 0

? [butLast.join(', '), last].join(' and ')

: last;

},

render: function () {

var songList = this.model.songs().length > 0

? [" has written " + this._englishList(this.model.songs().map\

(function (song) {

return "'" + song + "'"; }))]

: [];

console.log(this.name + songList);

return this;

}

};

var paulSimon = Object.create(SubscribableSongwriter).constructor(),

paulView = Object.create(SongwriterView).constructor(paulSimon, 'Paul Simon'\

);

paulSimon.addSong('Cecilia')

//=> Paul Simon has written 'Cecilia'

{}

paulSimon.songs()

//=> [ 'Cecilia' ]

Why would we do this? Well, for certain styles of programming, we care very much what prototypes are in an object’s prototype chain. Note:

Songwriter.isPrototypeOf(sweetBabyJames)

//=> false

Songwriter.isPrototypeOf(paulSimon)

//=> true

When we compose behaviours directly, we lose the ability to track their inclusion in the prototype chain. There is no baked-in way to ask whether an object includes the behaviour from AwardWinningSongwriter. So why don’t we always wire behaviours up with prototype chains? As we discussed earlier, prototype chains can only model trees, while composing behaviours provides much more freedom to mix and match compact, focused responsibilities.

When we choose model our behaviour with prototypes, it is still advantageous to use our model for composing behaviour. A naïve prototype chain with methods embedded directly in the prototypes has open recursion and suffers from coupling. Using encapsulated behaviours with prototypes decouples prototypes from their objects and from each other.

Transforming and Decorating Metaobjects

Going back to the Big Idea, there is value in having an algebra of metaobjects. We’ve just seen a protocol for encapsulating and composing metaobjects: Encapsulation works with composition to decouple metaobjects from each other, which in turn makes it easy to combine and recombine them as needed to form new metaobjects.

Composition is not the only mechanism for building metaobjects from other metaobjects. We can also transform metaobjects into new metaobjects. We’ve already seen one such transformation: encapsulate transformed an open metaobject into an encapsulated metaobject.

decorating methods

Here’s another function that transforms a metaobject into another metaobject:

function fluentByDefault (metaobject) {

var metaproto = Object.getPrototypeOf(metaobject),

fluentMetaobject = Object.create(metaproto),

key;

for (key in metaobject) {

if (typeof metaobject[key] === 'function') {

!function (key) {

fluentMetaobject[key] = function () {

var returnValue = metaobject[key].apply(this, arguments);

return typeof returnValue === 'undefined'

? this

: returnValue;

}

}(key);

}

}

return fluentMetaobject;

}

This takes a metaobject and returns another metaobject. The new metaobject delegates all of its methods to the original, but its return value protocol is decorated: any method that returns undefined will return this instead.

Note that we are not modifying a metaobject in place. Consider:

var Songwriter = encapsulate({

constructor: function () {

this._songs = [];

},

addSong: function (name) {

this._songs.push(name);

},

songs: function () {

return this._songs;

}

});

var FluentSongwriter = fluentByDefault(Songwriter);

var hall = Object.create(Songwriter),

oates = Object.create(FluentSongwriter);

hall.constructor();

//=> undefined

oates.constructor();

//=> {}

hall.addSong("your kiss is on my list");

//=> undefined

oates.addSong("your kiss is on my list");

//=> {}

Creating FluentSongwriter by transforming Songwriter does not modify Songwriter, it retains its behaviour and meaning. This differs from practice in some language communities, where it is commonplace to modify metaobjects in place. As we discussed in Closing Encapsulated Objects, encapsulated objects like Songwriter cannot be modified in place, but a naïve metaobject could easily be mutated:

var NaiveSongwriter = {

constructor: function () {

this._songs = [];

},

addSong: function (name) {

this._songs.push(name);

},

songs: function () {

return this._songs;

}

};

// ...

var oldInitialize = NaiveSongwriter.constructor,

oldAddSong = NaiveSongwriter.addSong;

NaiveSongwriter.constructor = function () {

oldInitialize.call(this);

return this;

}

NaiveSongwriter.addSong = function (song) {

oldAddSong.call(this, song);

return this;

}

This goes completely against the idea of an “algebra:” Modifying a metaobject couples the code that modifies the metaobject to every object that might use it. On a small scale this is manageable. But people have adopted this practice in languages like Ruby, and their experience with large-scale programs has produced decidedly mixed reviews.

On the whole, it is decidedly better to transform metaobjects by creating new metaobjects from them that may delegate to the original.

selective transformations

Our fluentByDefault transformer attempts to make all methods fluent. We can be more selective. Here’s a version that takes a metaobject and one or more “selectors” and then makes the methods matching teh selectors into methods that are fluent by default:

function selectorize (selectors) {

var fns = selectors.map(function (selector) {

var regex;

if (typeof selector === 'string' && selector[0] === '/') {

regex = new RegExp(selector.substring(1, selector.length-1));

return function (name) { return !!name.match(regex); };

}

else if (typeof selector === 'string') {

return function (name) { return name === selector; };

}

else if (typeof selector === 'function') {

return selector

}

});

return function select (name) {

return fns.some(function (test) { return test.call(this, name) }, this);

}

}

function fluentByDefault (metaobject) {

var selectors = [].slice.call(arguments, 1),

selectorFn = selectorize(selectors),

metaproto = Object.getPrototypeOf(metaobject),

fluentMetaobject = Object.create(metaproto),

key;

for (key in metaobject) {

if (typeof metaobject[key] === 'function' && selectorFn.call(metaobject, key)\

) {

!function (key) {

fluentMetaobject[key] = function () {

var returnValue = metaobject[key].apply(this, arguments);

return typeof returnValue === 'undefined'

? this

: returnValue;

}

}(key);

}

else fluentMetaobject[key] = metaobject[key];

}

return fluentMetaobject;

}

var Songwriter = encapsulate({

constructor: function () {

this._songs = [];

},

addSong: function (name) {

this._songs.push(name);

},

songs: function () {

return this._songs;

}

});

var FluentSongwriter = fluentByDefault(Songwriter, "initialize");

var hall = Object.create(Songwriter),

oates = Object.create(FluentSongwriter);

hall.constructor();

//=> undefined

oates.constructor();

//=> {}

hall.addSong("your kiss is on my list");

//=> undefined

oates.addSong("your kiss is on my list");

//=> undefined

This is a lot of work just to make things fluent. Let’s extract an inner helper function:

function fluentByDefault (metaobject) {

var selectors = [].slice.call(arguments, 1),

selectorFn = selectorize(selectors),

metaproto = Object.getPrototypeOf(metaobject),

decoratedMetaobject = Object.create(metaproto),

decorator = function (methodBody) {

return function () {

var returnValue = methodBody.apply(this, arguments);

return typeof returnValue === 'undefined'

? this

: returnValue;

}

},

key;

for (key in metaobject) {

if (typeof metaobject[key] === 'function' && selectorFn.call(metaobject, key)\

) {

decoratedMetaobject[key] = decorator(metaobject[key]);

}

else decoratedMetaobject[key] = metaobject[key];

}

return decoratedMetaobject;

}

And now we can extract the decorator parameter:

function decorate (decorator, metaobject) {

var selectors = [].slice.call(arguments, 2),

selectorFn = selectorize(selectors),

metaproto = Object.getPrototypeOf(metaobject),

decoratedMetaobject = Object.create(metaproto),

key;

for (key in metaobject) {

if (typeof metaobject[key] === 'function' && selectorFn.call(metaobject, key)\

) {

decoratedMetaobject[key] = decorator(metaobject[key]);

}

else decoratedMetaobject[key] = metaobject[key];

}

return decoratedMetaobject;

}

Our new decorate transformer can perform all kinds of decorations. We can borrow an idea from our method resolvers (it makes a great method decorator):

function after (decoration) {

return function (methodBody) {

return function () {

var methodBodyResult = methodBody.apply(this, arguments),

decorationResult = decoration.apply(this, arguments);

return typeof methodBodyResult === 'undefined'

? decorationResult

: methodBodyResult;

};

}

}

var logAfter = after(function () { console.log('after!'); });

var LoggingSongwriter = decorate(logAfter, Songwriter, 'addSong');

var taylor = Object.create(LoggingSongwriter);

taylor.constructor();

taylor.addSong("fire and rain");

//=> after!

undefined

It’s easy to extend this idea to include before, and around decorations:

function before (decoration) {

return function (methodBody) {

return function () {

var decorationResult = decoration.apply(this, arguments),

methodBodyResult = methodBody.apply(this, arguments)

return typeof methodBodyResult === 'undefined'

? decorationResult

: methodBodyResult;

};

}

}

function around (decoration) {

return function (methodBody) {

return function () {

return decoration.apply(this, [methodBody].concat([].slice.call(arguments, \

0)));

};

}

}

We can thus make new metaobjects out of old ones with functions that selectively decorate one or more of the metaobject’s methods.

the elements of decoupling style

This simple idea of writing a metaobject transformer is a direct analogue to writing a functional decorator. We are creating a new metaobject that is semantically similar to the old one, but without “monkey-patching” or mutating the existing metaobject.

The monkey-patching style works fine on many small projects, but as we scale up, we run into problems. Code will depend upon the original metaobject, and thus a change to the new code may break the old code.

inheritance and decoration

Playing Well with Classes

Throughout our examples of encapsulating and composing metaobjects, we have shown that they are still objects and can be used as prototypes. To recap, all of these idioms work:

// an unencapsulated metaobject

var PlainSongwriterMetaobject = {

constructor: function () {

this._songs = [];

return this.self;

},

addSong: function (name) {

this._songs.push(name);

return this.self;

},

songs: function () {

return this._songs;

}

};

var tracy = Object.create(PlainSongwriterMetaobject)

tracy.constructor();

// an encapsulated metaobject

var SongwriterMetaobject = encapsulate({

constructor: function () {

this._songs = [];

return this.self;

},

addSong: function (name) {

this._songs.push(name);

return this.self;

},

songs: function () {

return this._songs;

}

});

var tracy = Object.create(SongwriterMetaobject)

tracy.constructor();

// composed metaobjects

var HasAwardsMetaobject = encapsulate({

_awards: null,

constructor: function () {

this._awards = [];

return this;

},

addAward: function (name) {

this._awards.push(name);

return this;

},

awards: function () {

return this._awards;

}

});

var AwardWinningSongwriterMetaobject = composeMetaobjects(SongwriterMetaobject, H\

asAwardsMetaobject),

tracy = Object.create(AwardWinningSongwriterMetaobject);

tracy.constructor();

metaobjects and constructors

As we’ve discussed, many JavaScript programs use the following idiom to implement “classes:”

function Songwriter () {

this._songs = [];

}

Songwriter.prototype = {

addSong: function (name) {

this._songs.push(name);

return this.self;

},

songs: function () {

return this._songs;

}

}

var tracy = new Songwriter();

Its defining features are:

1. You call it like a function with the new keyword;

2. Behaviour is kept in its prototype property.

We can manually make such a “class” by wrapping a function around a metaobject:

function AwardWinningSongwriter () {

AwardWinningSongwriterMetaobject.constructor.apply(this, arguments);

}

AwardWinningSongwriter.prototype = AwardWinningSongwriterMetaobject;

We can automate this idiom. This Newable function takes an optional name and a metaobject, and returns a “class” that works with the new keyword:

function Newable (optionalName, metaobject) {

var name = typeof(optionalName) === 'string'

? optionalName

: '',

metaobject = arguments.length > 1

? metaobject

: optionalName,

constructor = (metaobject.constructor || function () {}),

source = "(function " + name + " () { " +

"var r = constructor.apply(this, arguments); " +

"return r === undefined ? this : r; " +

"})",

clazz = eval(source);

clazz.prototype = extend({}, metaobject);

delete clazz.prototype.constructor;

return clazz;

}

var AwardWinningSongwriter = Newable(AwardWinningSongwriterMetaobject);

// or Newable("AwardWinningSongwriter", AwardWinningSongwriterMetaobject);

var tracy = new AwardWinningSongwriter();

We can chain these classes to create a “class hierarchy” if we so desire. Most of the work is concerned with chaining constructor functions:

function Newable (optionalName, metaobject, optionalSuper) {

var name = typeof(optionalName) === 'string'

? optionalName

: '',

metaobject = typeof(optionalName) === 'string'

? metaobject

: optionalName,

superClazz = typeof(optionalName) === 'string'

? optionalSuper

: metaobject,

source = "(function " + name + " () { " +

"var r = constructor.apply(this, arguments); " +

"return r === undefined ? this : r; " +

"})",

clazz = eval(source),

constructor;

if (typeof(metaobject.constructor) === 'function' && typeof(optionalSuper) === \

'function') {

constructor = function () {

optionalSuper.apply(this, arguments);

return metaobject.constructor.apply(this, arguments);

}

}

else if (typeof(metaobject.constructor) === 'function') {

constructor = metaobject.constructor;

}

else if (typeof(optionalSuper) === 'function') {

constructor = optionalSuper;

}

else constructor = function () {}

clazz.prototype = extend(Object.create(optionalSuper.prototype), metaobject);

delete clazz.prototype.constructor;

return clazz;

}

Now we can formulate a different kind of AwardWinningSongwriter, one that wires up the behaviour from HasAwardsMetaobject and “extends” the constructor Songwriter:

var Songwriter = Newable('Songwriter', SongwriterMetaobject);

var AwardWinningSongwriter = Newable('AwardWinningSongwriter', HasAwardsMetaobjec\

t, Songwriter);

var tracy = new AwardWinningSongwriter();

tracy.addSong('Fast Car');

tracy.addAward('Grammy');

tracy.songs();

//=> [ 'Fast Car' ]

tracy.awards();

//=> [ 'Grammy' ]

Of course, we can have Newable extend any constructor function it likes, including those created with ES6’s class keyword. And other “classes” can extend the functions created by Newable.

Thus, we see that the metaobjects we create are fully compatible with the existing idioms we may encounter is a code base.