Created
January 21, 2013 18:43
-
-
Save ryankinal/4588250 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <em>If you haven't read my previous tutorial on <a title="Objects and the Prototype Chain" href="http://blog.javascriptroom.com/2013/01/14/objects-and-the-prototype-chain/">Objects and The Prototype Chain</a>, it would be a good idea to do so. This article builds on the concepts presented there. Object oriented programming with only those concepts, while possible, can get pretty verbose, so we often use common abstraction techniques - called Design Patterns - to simplify the task.</em> | |
| The Initializer Pattern is my favorite OO design pattern in JavaScript. I find it intuitive and easy to use. It's straightforward, and contains all the necessary internal knowledge of an object in the object itself. I'll get to each of these points as we go along, but for now, let's just take a look at the most basic concept of the pattern: an initialization function. | |
| <h2>In Comparison</h2> | |
| In the first article, using basic prototypal features, we see code that looks like this: | |
| [javascript] | |
| var point = { | |
| translate: function(x, y) { | |
| this.x += x; | |
| this.y += y; | |
| } | |
| }; | |
| var obj = Object.create(point); | |
| obj.x = 42; | |
| obj.y = 17; | |
| obj.translate(7, 9); | |
| [/javascript] | |
| Simple and straightforward, but a little verbose (especially with larger amounts of initial values), and the responsibility of assigning values lies outside the object itself. Let's see what happens with an initialization function, though: | |
| [javascript] | |
| var point = { | |
| init: function(x, y) { | |
| this.x = x; | |
| this.y = y; | |
| }, | |
| translate: function(x, y) { | |
| this.x += x; | |
| this.y += y; | |
| } | |
| }; | |
| var obj = Object.create(point); | |
| obj.x = 42; | |
| obj.y = 17; | |
| obj.translate(7, 9); | |
| [/javascript] | |
| <h2>What's Good?</h2> | |
| First, the object itself maintains control over what happens to the data is used. Sure, all its properties are still public and mutable, but a point determines how to interpret and initialize the values passed into the intitialization function. It can set defaults if the values are undefined, and it can massage the data into a proper format. | |
| Even more importantly, it does all of this <em>in one place.</em> If the way we treat the data needs to change, it will necessarily change for all of our points. This drastically affects the <a title="Don't Repeat Yourself" href="http://en.wikipedia.org/wiki/Don't_repeat_yourself">DRY</a>ness of our the code. This is not a feature unique to the Initializer Pattern; It is (or should be) common to all good design patterns. But it is a benefit nonetheless. | |
| This pattern also keeps inheritance simple and DRY. | |
| <h2>Inheritance</h2> | |
| Let's extend our <em>point</em> object into a 3D point and see what happens: | |
| [javascript] | |
| var point = { | |
| init: function(x, y) { | |
| this.x = x, | |
| this.y = y | |
| }, | |
| translate: function(x, y) { | |
| this.x += x; | |
| this.y += y; | |
| } | |
| }; | |
| var point3d = Object.create(point); | |
| point3d.init = function(x, y, z) | |
| { | |
| point.init.call(this, x, y); | |
| this.z = z; | |
| } | |
| point3d.translate = function(x, y, z) | |
| { | |
| point.translate.call(x, y); | |
| this.z += z; | |
| } | |
| var obj = Object.create(point3d); | |
| obj.init(42, 17, 29); | |
| obj.translate(7, 9, 12); | |
| [/javascript] | |
| First, initialization code is encapsulated inside the objects that care about it. This point has already been covered, but the granularity extends to inherited objects themselves. A 3D point is really just a 2D point with an extra dimension, and that's how it's logically represented in this structure. The first two dimensions are the responsibility of <em>point</em> (as it should be). | |
| Next, with no extra code, and no duplicated code, all necessary data is stored on the object itself. If you take a look at the prototype chain, you'll see that the prototype of <em>obj</em> (point3d) has only functions, and the prototype of the prototype of <em>obj</em> (point) also has only functions. This prevents the possibility of accidentally sharing data between objects inheriting from the same prototype. As long as you stick to the pattern, <em>this.x</em> will always refer to the right value. This is a benefit over other patterns, as well as a benefit over the base method of OOP in JavaScript. | |
| This is a very simple constructor, though. Maybe we want to increase the complexity a little, and add some default values. | |
| [javascript] | |
| var point = { | |
| init: function(x, y) { | |
| if (typeof x === 'undefined' || x === null) { | |
| this.x = 0; | |
| } else { | |
| this.x = x, | |
| } | |
| if (typeof y === 'undefined' || y === null) { | |
| this.y = 0; | |
| } else { | |
| this.y = x, | |
| } | |
| }, | |
| translate: function(x, y) { | |
| this.x += x; | |
| this.y += y; | |
| } | |
| }; | |
| var point3d = Object.create(point); | |
| point3d.init = function(x, y, z) | |
| { | |
| point.init.call(this, x, y); | |
| if (typeof z === 'undefined' || z === null) { | |
| this.z = 0; | |
| } else { | |
| this.z = x, | |
| } | |
| } | |
| point3d.translate = function(x, y, z) | |
| { | |
| point.translate.call(x, y); | |
| this.z += z; | |
| } | |
| var obj = Object.create(point3d); | |
| obj.init(42, 17, 29); | |
| obj.translate(7, 9, 12); | |
| [/javascript] | |
| At this point, the code reuse possibilities should become clearer. The amount of code is growing, but it's organized in such a way that it's still maintainable. Each object handles the data it needs to handle - nothing more, and nothing less. | |
| <h2>Some Final Notes</h2> | |
| The initialization function can obviously be named anything you want. I usually stick to <em>init</em> for a couple reasons: | |
| <ul> | |
| <li>It provides a uniform interface for object setup. If you're working with my code, you can probably call <em>init</em> to initialize my object, and if it doesn't have one, then it doesn't need to be initialized. If you want to use something more semantic in a particular case, it's okay, but uniform interfaces are a good idea.</li> | |
| <li>The abbreviation leaves off the annoying international spelling issues. As you may have noticed, I've been using the American English spelling (-ize) throughout this article, but the UK English spelling is initial<em>ise</em> (-ise). Leaving it of entirely circumvents the issue.</li> | |
| <li>I don't know how popular this pattern is. I really have no idea if anybody else uses it. But it hasn't done me wrong since I've been using it in the way presented, and I see very little reason why I would switch from it.</li> | |
| </ul> | |
| And there it is. The Initializer Pattern, in all its glory. Go make some stuff. | |
| <h2>About Me</h2> | |
| I wear several hats, and I do a lot of different things. By day, I'm a mild-mannered web developer for <a href="http://mckissock.com">McKissock Education</a>. By night, I'm CTO and Lead Developer of <a href="http://slicktext.com">SlickText.com - Text Message Marketing service</a>. I'm also a musician, songwriter, dancer, and pretty cool guy. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment