Thursday, April 15, 2010

JS Class Patterns

I've been in a lot of discussions lately about JavaScript design patterns, specifically around how to write a class. The two patterns in the lead are as follows:

Decorator pattern, new Foo() returns a decorated BaseClass object:

function Foo(bar) {
  var self = new BaseClass();

  var baz = 42;

  self.publicFn = function() {
    return bar + baz;
  };

  return self;

  function privateFn() {
    bar = 0;
  }
}

Prototype pattern, more traditional JS:

function Foo(bar) {
  BaseClass.call(this);

  this.bar_ = bar;
  this.baz_ = 42;
}
Foo.inheritsFrom(BaseClass);

Foo.prototype.publicFn = function() {
  return this.bar_;
};

Foo.prototype.privateFn_ = function() {
  this.bar_ = 0;
};

At first, I was 110% in the Prototype camp. It feels like how JS is "supposed" to be used and has some undeniable advantages. Object construction using prototype instead of decoration is much faster, uses less memory, and is usually considered a Good Thing™.

It also lets you know what type objects are in a more intuitive way. With the Prototype pattern, typeof new Foo() will return Foo, where as with the Decorator pattern typeof new Foo() will return BaseClass since what you really have is a decorated BaseClass object.

Using the prototype object to add functions to your class lets you also gives you a lot of flexibility regarding function overriding. It makes mocking objects much easier, and lets you wrap functions with things like logging on a global level instead on an instance by instance basis.

The Decorator pattern does have some advantages though. You can have privately scoped variables and functions. You don't have to worry about execution scope since you always have a variable "self" to reference. You definitely hear a lot less "is this this, or is this that... which this is this?!" when using the Decorator pattern.

Overall, the Decorator pattern behaves much more like other OO languages at the cost of performance and flexibility you get with the prototype object.

When listed out like that, I'm still at least 99% in the Prototype pattern camp. However there are some other key factors that have started to change my mind:

Talking to the team, we discovered that development speed has increased significantly by moving to the Decorator pattern. This could be attributed to a lack of JS knowledge by the developers since the Prototype pattern is fairly JS specific, but they are all very smart, talented engineers so the learning curve affects should be minimized. I think even the reduced debugging time from having the "self" variable has helped a lot. I've done a lot of JS development, and I know I forget to bind functions to the right "this" from time to time.

Test effectiveness has also been much better with the Decorator pattern. With actual private variables and functions there are less shortcuts taken in tests. We end up doing less pure "unit" tests and more domain tests, and since we don't have automated integration Selenium-ish tests for our current project's platform that has been quite helpful.

Also, on our project, the performance loss from not using the prototype object is basically a non-issue, so the Decorator pattern doesn't hurt us much in that regard. We are only constructing a handful of objects at a time (less than 100) so we're only sacrificing a couple milliseconds of CPU cycles if that.

I don't claim to have the all encompassing answer for which pattern is better, but for our project, I'm starting to lean more towards the Decorator pattern, or at least acknowledge how it's helped our development. Anything that can improve both development speed and quality wins major points in my book.

One of the reasons I bring this up on the blog is that the whole discussion is the perfect anti-cement cutting board story. In another world, someone could come in and impose cement cutting boards like "You need to use the Prototype pattern since that's the most JavaScripty way." Or even "Both those are wrong, use Crockford's latest and greatest pattern."

However, on our project, we, the developers, not only get try multiple patterns but discuss and come up with a solution that works best for us in the real world. These kinds of decisions couldn't come from anyone other than those in the trenches with real hands on experience.

Ultimately, we are the ones it matters most to since we are the ones that have to deliver a great product to our client. Even if we use a design pattern that loses us some academic brownie points, if we deliver a better product faster, we still win, our client wins, and that's what really matters.

No comments:

Post a Comment