Specify the kind of objects to create using a prototypical instance, and create new objects by copying this prototype.
Usage in JavaScript: |
|
high |
The Prototype Pattern creates new objects, but rather than creating non-initialized objects it returns objects that are initialized with values it copied from a prototype - or sample - object. The Prototype pattern is also referred to as the Properties pattern.
An example of where the Prototype pattern is useful is the initialization of business objects with values that match the default values in the database. The prototype object holds the default values that are copied over into a newly created business object.
Classical languages rarely use the Prototype pattern, but JavaScript being a prototypal language uses this pattern in the construction of new objects and their prototypes.
In the sample code we have a CustomerPrototype object that clones objects given a prototype object. Its constructor function accepts a prototype of type Customer. Calling the clone method will generate a new Customer object with its property values initialized with the prototype values.
This is the classical implementation of the Prototype pattern, but JavaScript can do this far more effectively using its built-in prototype facility. We will explore this in the JavaScript optimized code.
function CustomerPrototype(proto) { this.proto = proto; this.clone = function () { var customer = new Customer(); customer.first = proto.first; customer.last = proto.last; customer.status = proto.status; return customer; }; } function Customer(first, last, status) { this.first = first; this.last = last; this.status = status; this.say = function () { alert("name: " + this.first + " " + this.last + ", status: " + this.status); }; } function run() { var proto = new Customer("n/a", "n/a", "pending"); var prototype = new CustomerPrototype(proto); var customer = prototype.clone(); customer.say(); }Run
The Namespace pattern is applied to keep the code out of the global namespace. Our namespace is named Patterns.Classic. A Revealing Module named Prototype encapsulates all of Prototype's functions. It exposes the Customer constructor function and by association its prototype.
In the run function, first a default customer is created in which all properties have default values. Then a second customer is created by providing two property values: Kevin and Summer as first and last name respectively.
Notice that when overriding the defaults, we are not changing the prototype values. Instead, two new properties are added to the Customer object itself: first and last. These new property values hide the prototype values.
The Patterns object contains the namespace function which constructs namespaces non-destructively, that is, if a name already exists it won't overwrite it.
The log function is a helper which collects and displays results.
var Patterns = { namespace: function (name) { var parts = name.split("."); var ns = this; for (var i = 0, len = parts.length; i < len; i++) { ns[parts[i]] = ns[parts[i]] || {}; ns = ns[parts[i]]; } return ns; } }; Patterns.namespace("Classic").Prototype = (function () { function Customer(first, last, status) { if (first) this.first = first; if (last) this.last = last; if (status) this.status = status; } Customer.prototype = { say: function () { log.add("name: " + this.first + " " + this.last + ", status: " + this.status); }, first: "n/a", last: "n/a", status: "pending" }; return { Customer: Customer }; })(); // log helper var log = (function () { var log = ""; return { add: function (msg) { log += msg + "\n"; }, show: function () { alert(log); log = ""; } } })(); function run() { var proto = Patterns.Classic.Prototype; var customer = new proto.Customer(); customer.say(); var kevin = new proto.Customer("Kevin", "Summer"); kevin.say(); log.show(); }Run