18 May 2011

In programming terms, Guy is a simple composite of Person. Think of Guy as the wrapper around Person–Guys protect their inner Person (via encapsulation). Since this is a pattern, and therefore generic, we need to make the process of building Guy more generic.

//This helper function helps us create the composite Person   	
var compose = function (component) {
return function (action) {
var request = Array.prototype.slice.apply(arguments);
request.shift();
if (typeof action === "function") {
//we now *return* so we can make returnable
//methods outside of the class.
//It's a little more dangerous to our encapsulation
return action.apply(component, request);
} else if (typeof component[action] === "function") {
component[action].apply(component, request);
}
};
};

//generic "has" method because we're using *this* instead of the
//component var, more on this later
var has = function (what) {
var found = this.find(what), box, label;
if (found) {
box = found[0],
label = found[1];
return !!box[label];
}
return false;
};

//A "real" person, everyone has one of these,
//but you never see someone else's "real"/"inner" person
var Person = function (name, basics) {
this.name = name;
this.stuff = basics || {};
this.index = -1;
};

//People do neat things, like find their stuff
Person.prototype.find = function (where, keep) {
if (!where) {
return;
}
var label = where.split("."),
box = this.stuff;
while (label.length > 1) {
if (typeof box[label[0]] === "undefined") {
if (keep) {
box[label[0]] = {};
} else {
return;
}
} else {
box = box[label.shift()];
}
}
return [box, label[0]];
};

//People also figure out where to put things they're given
Person.prototype.keep = function (what, where) {
var found = this.find(where, true) || [this.stuff, this.index += 1],
box = found[0],
label = found[1];
//prevent someone from overwriting an object
//to effectively set a "private" value
if (box[label] == null) {
box[label] = what;
}
};

//A generic Guy, all guys protect their inner Person
var Guy = function (name, basics) {
//if you ask nicely, a Guy might grant special
//access to the inner Person
this.please = compose(new Person(name, basics));
};

//A Guy will show you some of his stuff, but he won't
//give you anything you can break (e.g., objects)
Guy.prototype.show = function (what) {
return this.please(function (what) {
var found, box, label;
if (typeof what === "undefined") {
//while inside a please function, this refers to
//our component--Person, not Guy
found = [this, "stuff"];
} else {
found = this.find(what);
}
box = found[0];
label = found[1];
if (typeof box[label] === "object") {
return JSON.stringify(box[label]);
} else {
return box[label];
}
}, what);
};

//To know what a Guy has, you need to ask nicely
Guy.prototype.has = function (what) {
//because we made the generic have method,
//we can use the reference here instead of the
//function literal, just easier to read/reuse
return this.please(has, what);
};

//Guys keep anything, if you don't tell him where
//to keep it, he'll put it in a numbered box,
Guy.prototype.keep = function (what, where) {
//Guy can just delegate this to his Person component
//who already knows how to do this (and doesn't return)
this.please("keep", what, where);
};

//meet Bob
var bob = new Guy("Bob");
//Give Bob some socks
bob.keep({socks: 2});
//Bob just tossed the socks into the first box he found
bob.show("0"); //"{"socks":2}"
//check Bob's clean shirts
bob.has("top drawer.clean shirts")); //false
//Tell Bob where to put his clean shirts
bob.keep({"clean shirts": 1}, "top drawer");
//Bob will show you the top drawer, but you can't change it
bob.show("top drawer"); //"{"clean shirts":1}"
//Bob tells you about his clean shirts
bob.show("top drawer.clean shirts"); //1
bob.has("top drawer.clean shirts"); //true


Discussion:

blog comments powered by Disqus