Is it possible to create an 'abstract class' in Javascript?

16

When creating a Javascript class whose attributes and methods are all static (for example, for storing predefined settings for a game), I would like to know if it is possible to define the class as abstract in a similar way as it can be (where the ' abstract ' clause is available or where you can simply define the constructor as protected).

In other words: considering this example available in JSFiddle , you can prevent the construction of instances of class' StaticTest (on line 36)? If the constructor (line 7) is not defined, the code does not execute (generating the exception "Uncaught ReferenceError: StaticTest is not defined").

// INICIO - Apenas para evitar poluir o espaço global
(function(){

    /**
     * Construtor da classe (é realmente necessário?).
     */
    var AbstractTest = function() {
    };

    /**
     * Atributo com o texto de teste.
     */
    AbstractTest.Attribute = "O atributo diz: Olá Planeta!";

    /**
     * Método que retorna o texto do teste.
     * @return String com o texto de teste.
     */
    AbstractTest.Method = function() {
        return "O método diz: Olá Mundo!";
    }

    // Atribui a classe ao escopo global 'window'
    window.AbstractTest = AbstractTest;

// FIM - Apenas para evitar poluir o espaço global
}());

/**
 * Função de teste de instanciação.
 * @return String com o texto de teste de uma instância criada.
 */
window.testInstantiation = function() {

    // A dúvida é sobre a possibilidade de impedir a execução dessa linha:
    var oTest = new AbstractTest();

    oTest.Attribute = "O atributo na nova instância diz: Olá Brasil!";
    return oTest.Attribute + " e " + AbstractTest.Attribute;
}

Note: the question is only a curiosity about the characteristics of the language; not that the possibility of instantiation of the class is necessarily a problem.

EDIT: Changed to correct and use the term "abstract" instead of "static."

    
asked by anonymous 14.01.2014 / 02:05

4 answers

6

In JavaScript there are no static classes, properties, or methods. The closest to this are properties and methods assigned directly to a constructor (as you did), or a simple literal object.

If you use a literal object to set StaticTest , then new StaticTest() will generate a TypeError. It is a way of solving the problem (with try..catch around the line that generates the exception).

Another way is to change the builder's return. I do not understand exactly what you want in the test, but it is possible to return the object StaticTest , or any other object:

var StaticTest = function() {
    // return StaticTest;
    // ou
    // return {};
};

But be careful: if you try to return a value that is not an object (as false ), the constructor will return this , that is, the newly created instance.

Considering your edition that changes "static" to "abstract", I think the best way would be to use a literal object, since the intention is to avoid instantiation:

var StaticTest = {
    attribute: "O atributo diz: Olá Planeta!",
    method: function(){}
    // etc.
};

Thus,% w_of% would generate an exception, which you can catch with% w_%:

try {
    new StaticTest();
} catch(e) {
    // trate a exceção aqui
}
    
14.01.2014 / 02:46
9

You can create an immutable object in JavaScript using the Object.freeze , and an immutable reference using the Object.defineProperty :

// Atribui a classe ao escopo global 'window'
Object.freeze(StaticTest);
Object.defineProperty(window, "StaticTest", { 
    value:StaticTest,
    configurable:false,
    writable:false
});

// Todas essas tentativas vão falhar silenciosamente:
// (ou lançar uma exceção, se o modo "strict" estiver ativado)
StaticTest = outroObjeto;
window.StaticTest = outroObjeto;
StaticTest.StaticAttribute = outroValor;
delete StaticTest.StaticAttribute;

As for preventing an object from being inherited , I do not know of any way to do that, and I do not think it's possible at all.

OO Classical vs. Prototypic

Notice that you asked about classes , but I gave my answer by speaking only of objects . Because? Simply because, strictly speaking, JavaScript does not have the concept of "classes".

In the classical object orientation used by the vast majority of languages that follow this paradigm, classes and objects (or instances) are distinct concepts: the class defines the "structure" and "behavior" of its objects, and each object belongs to to a single class. Class inherits from class, so instances of the specific class have structure and behavior similar to instances of the general class.

In prototypical OO, there are only objects. An object defines its own structure and behavior independently of the others. To reuse these features in other objects, they inherit directly from the existing object (here called prototype ), modifying whatever you want and keeping (sharing) the rest. There are no classes, just constructor functions .

Inheritance in JavaScript

For historical reasons, while JavaScript is conceptually a language that follows prototypical OO, its syntax tries to "hide" the fact - making it more a bit like the classic. The result is a "salad", as I will exemplify below:

// Objeto simples: sem classe, sem construtor
var obj = {
    atributo:"planeta",
    metodo:function() {
        return "Olá, " + this.atributo + "!";
    }
}

// Construindo um objeto que herda de "obj"
function herdarDeObj() { }
herdarDeObj.prototype = obj;

var obj2 = new herdarDeObj();
alert(obj2.metodo()); // "Olá, planeta!"
obj2.atributo = "mundo";
alert(obj2.metodo()); // "Olá, mundo!"

alert( obj.isPrototypeOf(obj2) ); // true
alert( obj === Object.getPrototypeOf(obj2) ); // true

// Sintaxe confusa
alert( obj === obj2.prototype ); // false
alert( obj2.prototype ); // undefined

alert( obj === herdarDeObj.prototype ); // true
alert( obj === Object.getPrototypeOf(herdarDeObj) ); // false
alert( Object.getPrototypeOf(herdarDeObj) ); // function Empty() {}
                                             // (varia conforme o browser)

alert( obj2 instanceof obj ); // false
alert( obj2 instanceof herdarDeObj ); // true
herdarDeObj.foo = "bar";
alert( obj2.foo ); // undefined
obj.foo = "baz";
alert( obj2.foo ); // "baz"

As you can see, we have two objects obj and obj2 in which the second inherits from the first (or: the first is a prototype of the second). However, JavaScript "hides" this simple relationship, forcing us to create a constructor method, assign it the prototype property and invoke it using the new keyword.

But note that obj is not prototype of herdarDeObj - it is a prototype of built objects using the new herdarDeObj() command. The constructor is a normal function, so much so that its prototype is the "empty function".

Probably because of this fact (of the constructor defining everything about the object - both the initial attributes, placed in the body of the constructor through this.atr = val , and the prototype, that of whom the object will inherit) with the "class" of the object. And for the convenience that this constructor method offers, rarely anyone uses [explicitly] prototypical inheritance in practice, so much so that there are plans to introduce classic OO concepts into future versions of JavaScript. Maybe someday, what you ask is in fact possible.

Conclusion

Since there are no classes in JavaScript, it does not make sense to speak of "static classes" (not even "static attributes" or "class attributes"). If you want to expose a collection of attributes and methods through a given name, the most natural is to do this using a simple object:

window.StaticTest = {
    StaticAttribute:"O atributo estático diz: Olá Planeta!",
    StaticMethod:function() {
        return "O método estático diz: Olá Mundo!";
    }
};

You can make the object and reference immutable, as I have explained, but can not prevent other objects from inheriting from it: anyone can create a new constructor function, assign StaticTest to their prototype , and call that function - producing objects that inherit from StaticTest .

    
14.01.2014 / 07:07
7

There is a serious confusion here about class and member concepts (attributes) in the question.

Static Attributes

They are those that, regardless of the instance of the class / function used, return the same value or same object instance.

The best example of this in Javascript is the use of prototype, as we can see in the @mgibsonbr response.

Immovable, invariant, or constant objects

We can not confuse static with immutable . In Java, for example, an immutable variable or variable is declared with final and not with static .

In javascript, it seems that the best option is Object.freeze , as we can see in the @mgibsonbr response.

Visibility

It was quoted in the question to prevent the external instantiation of the class. In languages like Java, this is achieved by creating a private constructor. See again, this does not have to do with the concepts of static or immutable .

Conclusion

To achieve your goal, use a unique setting, my suggestion is simply to use a global variable.

There are some more advanced techniques for implementing the Singleton Javascript project pattern ( here , here and #), but if you have control of the code, just set an access pattern if you follow it throughout the development. There is no technique that saves you from following your own design , so do not spend too much time protecting your code yourself.

On the other hand, for the creation of libraries, frameworks and APIs the story is another.

    
14.01.2014 / 12:32
3

One way to build an abstract "class", with the usual inheritance of Javascript prototypes, is to use a constructor like this:

/**
 * Construtor da classe abstrata.
 * @abstract
 * @constructor
 */
var Base = function() {
    if (this.constructor === Base) {
      throw new Error("Não pode instanciar classe abstrata!");
    }

    // .... código do construtor
};

The constructor checks if it was called a directive (as in new Base() ) with the property constructor , and throws an exception if this happened.

This "class" can only be used if it is derived:

/**
 * Construtor da classe derivada.
 * @constructor
 */
var Derived = function() {
    Base.apply(this, arguments);
};

Derived.prototype = Object.create(Base.prototype);
Derived.prototype.constructor = Derived;

The derived class calls the base class constructor with apply , and creates its prototype based on that of the base class with Object.create . It is also very important to assign the constructor correctly to Derived , otherwise you will not be able to create instances of Derived .

A longer example can be found here .

Note the use of JSDoc @abstract .

    
14.01.2014 / 20:02