Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it bad practice to apply class-based design to JavaScript programs?

Tags:

javascript

JavaScript is a prototyped-based language, and yet it has the ability to mimic some of the features of class-based object-oriented languages. For example, JavaScript does not have a concept of public and private members, but through the magic of closures, it's still possible to provide the same functionality. Similarly, method overloading, interfaces, namespaces and abstract classes can all be added in one way or another.

Lately, as I've been programming in JavaScript, I've felt like I'm trying to turn it into a class-based language instead of using it in the way it's meant to be used. It seems like I'm trying to force the language to conform to what I'm used to.

The following is some JavaScript code I've written recently. It's purpose is to abstract away some of the effort involved in drawing to the HTML5 canvas element.

/*
Defines the Drawing namespace.
*/
var Drawing = {};

/*
Abstract base which represents an element to be drawn on the screen.
@param The graphical context in which this Node is drawn.
@param position The position of the center of this Node.
*/
Drawing.Node = function(context, position) {

    return {

        /*
        The method which performs the actual drawing code for this Node.  This method must be overridden in any subclasses of Node.
        */
        draw: function() {
            throw Exception.MethodNotOverridden;
        },

        /*
        Returns the graphical context for this Node.
        @return The graphical context for this Node.
        */
        getContext: function() {
            return context;
        },

        /*
        Returns the position of this Node.
        @return The position of this Node.
        */
        getPosition: function() {
            return position;
        },

        /*
        Sets the position of this Node.
        @param thePosition The position of this Node.
        */
        setPosition: function(thePosition) {
            position = thePosition;
        }
    };
}

/*
Define the shape namespace.
*/
var Shape = {};

/*
A circle shape implementation of Drawing.Node.
@param context The graphical context in which this Circle is drawn.
@param position The center of this Circle.
@param radius The radius of this circle.
@praram color The color of this circle.
*/
Shape.Circle = function(context, position, radius, color) {

    //check the parameters
    if (radius < 0)
        throw Exception.InvalidArgument;

    var node = Drawing.Node(context, position);

    //overload the node drawing method
    node.draw = function() {

        var context = this.getContext();
        var position = this.getPosition();

        context.fillStyle = color;
        context.beginPath();
        context.arc(position.x, position.y, radius, 0, Math.PI*2, true);
        context.closePath();
        context.fill();
    }

    /*
    Returns the radius of this Circle.
    @return The radius of this Circle.
    */
    node.getRadius = function() {
        return radius;
    };

    /*
    Sets the radius of this Circle.
    @param theRadius The new radius of this circle.
    */
    node.setRadius = function(theRadius) {
        radius = theRadius;
    };

    /*
    Returns the color of this Circle.
    @return The color of this Circle.
    */
    node.getColor = function() {
        return color;
    };

    /*
    Sets the color of this Circle.
    @param theColor The new color of this Circle.
    */
    node.setColor = function(theColor) {
        color = theColor;
    };

    //return the node
    return node;
};

The code works exactly like it should for a user of Shape.Circle, but it feels like it's held together with Duct Tape. Can somebody provide some insight on this?

like image 260
LandonSchropp Avatar asked Dec 20 '10 07:12

LandonSchropp


People also ask

Is it bad to use classes in JavaScript?

Classes seem pretty cool! So what's so bad about using them in JS? There's nothing inherently bad about using this pattern in JS. However, the problem I've frequently encountered is that this is sometimes the only pattern, or a frequently misused pattern, in a JS codebase.

Is it good to use classes in JavaScript?

Classes in JavaScript are syntactic sugar over the prototype-based inheritance model which we use to implement OOP concepts. Thus the introduction of classes in JS made it easier for developers to build software around OOP concepts.

Why JavaScript is not class-based?

That's because JavaScript doesn't have a concept of methods. Functions are first-class in JavaScript, and they can have properties or be properties of other objects. A class constructor creates an instance of the class. A constructor in JavaScript is just a plain old function that returns an object.

Are classes still used in JavaScript?

Classes themselves are normal JavaScript values as well, and have their own prototype chains. In fact, most plain JavaScript functions can be used as constructors — you use the new operator with a constructor function to create a new object.


1 Answers

This is going to be an opinion-driven question. But I'll throw in my $.02.

tl/dr: don't worry too much about this. JavaScript is pretty flexible and can support a lot of ways of doing things. Well-organized is well-organized. You're probably fine.

More detailed answer:

1) Use classes where they make sense: where the problem domain fits class-ish / class hierarchy modeling. A problem domain where you've got a variety of shape objects that have common methods inherited from a base class and other polymorphic methods... well, that's (literally) a textbook example of a case where the class hierarchy is obvious and probably useful, and class-focused code is going to make sense there and there's nothing wrong with it.

2) You don't even have to use closures/module patterns/whatever. When you're writing classes, most of the time there's nothing wrong with making use of the native class-ish functionality available in JavaScript -- just define the constructor, and then define the prototype object for the constructor and put your methods on it. When you want to inherit from that class, assign the prototype object of the subclass to an instance of the class from which you're deriving.

(For example:

Drawing.Node = (function() {

    var Node = function (context,position) {
        this.context = context;
        this.position = position;
    }

    Node.prototype = {
        draw: function() { throw Exception.MethodNotOverridden; },
        getContext: function() { return this.context; },
        getPosition: function() { return this.position; },
        setPosition: function(newPosition) { this.position = newPosition; }
    };

    return Node;
})();

Shape.Circle = (function () {

    var Circle = // Circle constructor function

    Circle.prototype = new Draw.Node;

    Circle.prototype.overriddenmethod1 = function () {

    }

    Circle.prototype.overriddenmethod2 = function () {

    }

    return Circle;
})()

)

What about private members/methods? This is an opinion, but most of the time, I think privacy as a runtime-enforced mechanism is overused and even abused. Developers have a lot to do; they'd probably rather not pay attention to the internals of any given abstraction unless it leaks something noxious. If your classes don't cause problems, throw/return useful errors, provide genuinely useful methods, and are documented well enough, you won't need any kind of privacy enforcement mechanism because everyone will be so happy with the work your classes are saving them they won't ever peer inside. If your classes don't meet that standard, well, the lack of a privacy enforcement mechanism is not your real problem.

There is an exception to this, and that's when you have JavaScript code from different (and often untrusted) sources mixing within a page/app. At that point, for security reasons, you sometimes have to carefully think about isolating some crucial functions/methods within a given scope that your code and your code alone has access to.

Edit/Addendum

In answer to the question about why I've got those immediately evaluated functions, consider this alternate way of writing the Drawing.Node definition:

Drawing.Node = function (context,position) {
    this.context = context;
    this.position = position;
}

Drawing.Node.prototype = {
    draw: function() { throw Exception.MethodNotOverridden; },
    getContext: function() { return this.context; },
    getPosition: function() { return this.position; },
    setPosition: function(newPosition) { this.position = newPosition; }
};

This does exactly the same thing as the code above. It's also, IMHO, totally acceptable, and possibly a little clearer and less tricksy.

On the other hand, I find that putting all that inside the scope of an immediately executed anonymous function gives me at least two advantages:

  1. If I do decide I need to define any private methods or otherwise do some setup work that's only relevant to that specific class definition, it gives me a nice private scope to work in.

  2. If I decide I need to move Node's location in the namespacing object hierarchy somewhere else, it's kindof convenient if everything related to its definition is all tied up in one convenient spot.

Sometimes these advantages are small. Sometimes they're a little more compelling. YMMV.

like image 176
Weston C Avatar answered Nov 15 '22 14:11

Weston C