Является ли плохая практика применять класс-дизайн к программам 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, но похоже, что он удерживается вместе с канальной лентой. Может ли кто-нибудь объяснить это?

Ответ 1

Это будет вопрос, основанный на мнении. Но я заберу $.02.

tl/dr: не слишком беспокоитесь об этом. JavaScript довольно гибкий и может поддерживать множество способов делать что-то. Хорошо организованная организация хорошо организована. Вы, наверное, хорошо.

Более подробный ответ:

1) Используйте классы, где они имеют смысл: где проблемная область подходит для моделирования иерархии классов/классов. Проблемная область, в которой у вас есть множество объектов формы, которые имеют общие методы, унаследованные от базового класса и других полиморфных методов... ну, это (буквально) пример учебника случая, когда иерархия классов очевидна и, вероятно, полезна, и код, ориентированный на класс, будет иметь смысл там, и в этом нет ничего плохого.

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-код из разных (и часто ненадежных) источников, смешивающих внутри страницы/приложения. В этот момент, по соображениям безопасности, вам иногда приходится тщательно думать об изоляции некоторых важных функций/методов в рамках данной области, к которой имеет доступ только ваш код и ваш код.

Edit/Добавление

В ответ на вопрос о том, почему у меня есть эти сразу оцененные функции, рассмотрите этот альтернативный способ записи определения 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; }
};

Это делает то же самое, что и код выше. Это также, ИМХО, вполне приемлемо и, возможно, немного яснее и менее сложно.

С другой стороны, я нахожу, что помещение всего, что входит в область сразу исполняемой анонимной функции, дает мне как минимум два преимущества:

  • Если я решаю, что мне нужно определить какие-либо частные методы или выполнить какую-либо работу по настройке, относящуюся только к определенному определению класса, она дает мне приятную личную область для работы.

  • Если я решил, что мне нужно перемещать расположение Node в иерархию объектов namespacing в другом месте, это удобно, если все, связанное с его определением, связано в одном удобном месте.

Иногда эти преимущества малы. Иногда они немного более убедительны. YMMV.

Ответ 2

Я верю в 99% случаев. Этот код выглядит как Java. Он не использует наследование прототипов. Он создает новые методы для каждого создания объектов.

Особенно это: throw Exception.MethodNotOverridden. Просто тип JS-кода, который я принимаю своих программистов в конференц-зал.

Несколько месяцев назад я написал сообщение о чрезмерной абстракции в JavaScript: http://glebm.blogspot.com/2010/10/object-oriented-javascript-is-evil.html

Этот вопрос дает более подробную информацию по теме: https://stackoverflow.com/info/3915128/object-oriented-vs-functional-javascript