将基于类的设计应用于JavaScript程序是不好的做法吗?

时间:2010-12-20 07:48:47

标签: javascript

JavaScript是一种基于原型的语言,但它能够模仿基于类的面向对象语言的某些功能。例如,JavaScript没有公共和私有成员的概念,但通过闭包的魔力,它仍然可以提供相同的功能。类似地,方法重载,接口,命名空间和抽象类都可以以这种或那种方式添加。

最近,由于我一直在使用JavaScript编程,我觉得我正在尝试将其转换为基于类的语言,而不是按照它的使用方式使用它。似乎我试图强迫语言符合我以前的习惯。

以下是我最近编写的一些JavaScript代码。它的目的是抽象出绘制到HTML5画布元素所涉及的一些工作。

/*
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;
};

代码的工作方式与Shape.Circle的用户完全一样,但感觉它与Duct Tape一起使用。有人可以对此提供一些见解吗?

2 个答案:

答案 0 :(得分:13)

这将是一个由观点驱动的问题。但我会投入我的$ .02。

tl / dr:不要太担心这个。 JavaScript非常灵活,可以支持很多方法。组织良好,组织良好。你可能没事。

更详细的答案:

1)使用有意义的类:问题域适合class-ish / class层次结构建模。一个问题域,你有各种各样的形状对象,这些对象具有从基类继承的常用方法和其他多态方法......好吧,这就是(字面上)一个教科书示例,其中类层次结构是显而易见的,可能是有用的而且,以类为重点的代码在那里是有意义的,并且它没有任何问题。

2)你甚至不必使用闭包/模块模式/等等。当你编写类时,大多数时候使用JavaScript中可用的本机类功能没有任何问题 - 只需定义构造函数,然后为构造函数定义原型对象并将方法放在其上。如果要从该类继承,请将子类的原型对象分配给要派生的类的实例。

(例如:

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;
})()

私人会员/方法怎么样?这是一种观点,但大多数时候,我认为隐私作为一种运行时强制机制被过度使用甚至被滥用。开发人员有很多事情要做;他们可能宁愿不注意任何给定抽象的内部,除非它泄漏了一些有害的东西。如果您的类不会导致问题,抛出/返回有用的错误,提供真正有用的方法,并且记录得很好,您将不需要任何类型的隐私执行机制,因为每个人都会对您的课程所节省的工作感到满意他们不会在里面同行。如果您的课程不符合该标准,那么缺乏隐私执行机制并不是您真正的问题。

这是一个例外,当你在页面/应用程序中混合来自不同(通常是不受信任的)来源的JavaScript代码时。此时,出于安全原因,您有时必须仔细考虑隔离代码和代码本身可以访问的给定范围内的一些关键函数/方法。

修改/附录

在回答有关为什么我有这些立即评估函数的问题时,请考虑这种编写Drawing.Node定义的替代方法:

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; }
};

这与上面的代码完全相同。它也是,恕我直言,完全可以接受,可能更清晰,更不狡猾。

另一方面,我发现将所有内容放在一个立即执行的匿名函数的范围内,这给了我至少两个好处:

  1. 如果我确定我需要定义任何私有方法或者进行一些仅与特定类定义相关的设置工作,那么它为我提供了一个很好的私有范围。

  2. 如果我决定需要在其他地方的名称空间对象层次结构中移动Node的位置,那么如果与其定义相关的所有内容全部绑定在一个方便的位置,则会很方便。

  3. 有时这些优势很小。有时他们会更有吸引力。因人而异。

答案 1 :(得分:2)

我相信99%的案例。 这段代码看起来像Java。它不使用原型继承。它为每个对象创建创建新方法。

特别是:throw Exception.MethodNotOverridden。只是我将程序员带到会议室的那种JS代码。

几个月前,我写了一篇关于JavaScript过度抽象的帖子:http://glebm.blogspot.com/2010/10/object-oriented-javascript-is-evil.html

此问题提供了有关该主题的更多见解:https://stackoverflow.com/questions/3915128/object-oriented-vs-functional-javascript