I'm looking for a sane solution to JavaScript's only-one-constructor problem. So let's say we have a class Point and we want to allow object creation from coordinates.
I'm gonna ignore type-checking in all these examples.
function Point(x, y) {
this.x = x;
this.y = y;
}
Easy. How about creating points from other points?
function Point(x, y) {
if (!y /* && x instanceof Point */) {
y = x.y;
x = x.x;
}
this.x = x;
this.y = y;
}
This turns into a nightmare quickly. So what I want is a design pattern that decouples these two constructors (or splits the one into two, rather). Objective-C has a nice pattern for this. ObjC people create objects with something.
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.withPoint = function(point) {
return new Point(point.x, point.y);
};
I like this a lot, so far. But now we have two different syntaxes.
var a = new Point(4, 2);
var b = Point.withPoint(a);
Alright that's easy enough, no? Just add Point.withCoordinates. But what about the constructor then? Hide it? I don't know. I guess this is where you come in.
And here's what I've decided to go with:
var Point = {
withCoordinates: function(x, y) {
if (typeof x == 'number' && typeof y == 'number') {
this.x = x;
this.y = y;
return this;
}
throw TypeError('expected two numbers');
},
withPoint: function(point) {
if (typeof point.x == 'number' && typeof point.y == 'number') {
this.withCoordinates(point.x, point.y);
return this;
}
throw TypeError('expected a point');
}
};
var a = Object.create(Point).withCoordinates(0, 0);
var b = Object.create(Point).withPoint(a);
Pros:
- No boilerplate
- Descriptive syntax/API
- Scales well
- Functional
- Easy to test
Cons:
- Instances don't know whether they're initialized or not
- Can't just add properties to a class (compare
Number.MAX_SAFE_INTEGER)
Notice the type-checks in Point.withPoint. It allows duck-typed points like click events.
function onClick(event) {
var position = Object.create(Point).withPoint(event);
}
Also notice the lack of zero-initialization in some sort of default ctor. Points are actually a really good example for why that's not always a good idea.