Object Oriented Programming for Javascript Dummies - Part 2
Tuesday, 6th November 2012, 15:33
Following on from the previous part, this time we are going to go from simple objects to full blown class hierarchies with a real world example.
So, we can represent any group of variables and related functions as an object, which we can create instances of and use. In the last part we just had an object that represented an animal, but this time we're going to focus on something far less esoteric, server side webpages.
This approach works nicely with Node.js, so this is what we'll base our examples on today.
A Base Class
First up, if we are to serve a webpage of any kind, we need to encompass all the basics that are generic to all webpages in our object. You'll see why we do this later, if you don't already see it now. We could get specialised and make it so our object does exactly what we want for a specific type of page, but we don't want to do that just yet.
The reason is, we are making a Base Class of object, inheriting the terminology from C++, and probably whatever that inherited it from. Think of a class as synonymous with "type of", so we create a class of object and then we can create instances of those object classes.
Let's do some code!
function wsBase(args)
{
// Some important members
this.rendered = false;
this.status = 200;
this.contentType = "text/html";
this.title = "Blank Page";
this.body = "<p>A Blank Page</p>";
// We'll need these for stuff
for (key in args)
this[key] = args[key];
}
Here is our class, wsBase, which we declare as a function, since everything is an object in Javascript, our wsBase function is an object. We call this function a constructor, because it constructs the various parts of our object.
We set a few simple flags that relate to the type of content we'll be sending to the client, and then the last thing we do is copy any properties from the object passed as args. That lets us pass the response and request variables, plus anything else we fancy later on, like perhaps a database handle. It also lets us override any of the variables we set like contentType whilst creating the object, saving us from having to change them later.
Here is an example of how we create an instance of this class:
var wpOurWebpage = new wsBase({ request: request, response: response });
Now, obviously this class doesn't do anything yet, so we need to add a few simple additional functions to it.
wsBase.prototype.preparePage = function(callback)
{
if (callback)
callback();
}
This function will do things that may require asynchronous actions such as pulling information from a database, loading a file from disk, pulling data from the network, that kind of thing. We shouldn't do that sort of thing as part of the constructor because during that time there may be parts of the object we haven't set up properly yet. This sort of thing may seem unnecessary now, but later on we are going to make things more complicated, so it will be important then.
Obviously our simple basic webpage does nothing like the above, so it just calls a provided callback function to effectively say, "I'm finished!".
Now we need to render the page we've prepared, so...
wsBase.prototype.renderPage = function(callback)
{
if (this.rendered)
throw("Page rendered twice");
this.rendered = true;
this.response.writeHead(this.status, { "Content-Type": this.contentType, "X-Frame-Options": "SameOrigin" });
this.response.end(<html><head><title>" + this.title + "</title></head><body>" + this.body + "</body</html>);
if (callback)
callback();
}
In this function, we throw an exception if the page has already been rendered, because that should never happen and is a sign of bad programming logic.
Failing that, we send our rendered page to the response object, and call a callback function if it has been provided. It's unlikely you'd want to provide one most of the time, but handy to have the option.
Here is how we use the above class in action:
var wpOurWebpage = new wsBase({ request: request, response: response });
wpOurWebpage.preparePage(function() {
wpOurWebpage.renderPage();
});
Now on it's own, the above is pretty simple and not very useful. What we really need is to do something more specific, and that we do through something called Inheritance.
Inheritance
So, what do we do if we want to take our above simple webpage and customise it? The old, bad way would involve a load of conditional statements, but OOP changed all that for us. Firstly, we need to write a function which creates a new object and inherits all the functions of the old one. The following is the best I've found so far, and it comes via Sitepoint:
function inheritObject(descendant, parent) {
var sConstructor = parent.toString();
var aMatch = sConstructor.match( /\s*function (.*)\(/ );
if ( aMatch != null ) { descendant.prototype[aMatch[1]] = parent; }
for (var m in parent.prototype) {
descendant.prototype[m] = parent.prototype[m];
}
};
Now armed with this function, we can do the following:
function wsHome(args)
{
this.wsBase(args);
this.title = "Home";
}
inheritObject(wsHome, wsBase);
wsHome.prototype.preparePage = function(callback)
{
setTimeout(function() {
this.body = "<p>This page took at least a second to make!</p>";
callback();
}, 1000);
}
Probably the only confusing line here is the one that calls this.wsBase(args), don't panic! Our inheritObject function actually copies the constructor of the parent class into a member of this class, so you can call it and have it set up all the things you'd otherwise have to duplicate yourself.
And the code to use our home page? You'll already pretty much know it:
var wpOurWebpage = new wsHome({ request: request, response: response });
wpOurWebpage.preparePage(function() {
wpOurWebpage.renderPage();
});
What should we do next? Well ideally if your page has a pretty standard look across all pages, perhaps you have the same basic layout, maybe you want to create a class that inherits from the base class, that creates all these elements for you. Then your home page class would inherit from that class instead of the base one.
If you have pages which require user authentication, you could create a class off the default look class that adds that functionality, and then inherit everything that needs to know about users from that.
What Goes in What Class?
This is the crux of good OOP design, and it's actually very simple. Just think of it as a tree, with the base class at the top. Now, you should already be used to programming basics, so hold onto the thought of putting code you keep using over and over again into a function, then calling the function instead of repeating the code.
Classes are the same thing, if a function has to be repeated, then it should go nearer the top of the hierarchy of inherited classes. That's all there is to it! OOP is really just about reusing code more than anything else.
So in our above example, on our wsHome class supposing we want to add a script like jQuery to the header. Well we could do one of two things, we could add a renderPage function to our wsHome class which replaces the wsBase one and adds in that script. But obviously that doesn't satisfy our all important code re-use condition.
If we want to add scripts, we should implement that facility in wsBase, perhaps as an array, and then the facility becomes available to all inheriting pages.
Remember, generic/reusable goes up the tree, specific/non-reusable goes down tree.
Abstract Classes
Before we end this quick guide on OOP, there are two more things we should cover. The first is abstract classes. What are these I hear you ask? Well put most simply, they are a class which is never designed to be instantiated, only inherited from.
We should really make our wsBase class an abstract one, because we are never going to really create a wsBase object, only ones which inherit from it. The easiest way to do that is take one of the functions which should be duplicated in an inheriting class, and make it throw an error.
Our preparePage function does nothing in wsBase, and every inheriting class that isn't also abstract, should re-implement it. So we'll do this:
wsBase.prototype.preparePage = function(callback)
{
throw("preparePage not defined in inheriting class");
}
Voila, wsBase is now an abstract class. If we accidentally create a class object that inherits from it and we forget to implement our own preparePage function, we'll soon know about it.
Multiple Inheritance
Hopefully you can see the benefits of inheriting a set of functions and variables from a parent class, but also maybe you can now begin to see how inheriting from not one but two classes might be handy on occasions.
Alas I have no function to do that for you... yet. :)
Last Words
This is by no means a definitive guide to OOP in general or OOP techniques in Javascript. There are many ways of doing similar things, I've just used the above method as an introduction. You should really investigate and play with it, and most importantly understand the pitfalls and pros/cons of various methods before you commit to one or another.
I'm personally not entirely happy with the inheritObject function, I can see a way of doing it which allows for multiple inheritance and automatic calling of parent constructors. When I code that, I'll post it.