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

Asked

Viewed 2,589 times

17

When creating a Javascript class whose attributes and methods are all static (for example, for storing preset settings for a game), I wonder if it is possible to define the class as abstract in a similar way to what can be accomplished in other languages (where the clause 'Abstract' is available or you can simply set the constructor as protected).

In other words: recital this example available in Jsfiddle, Is it possible to prevent the construction of instances of the 'Statictest' class (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 language; not that the possibility of class instantiation is necessarily a problem.

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

  • 1

    +1 why is a good question! However, I will add an answer to define some concepts that are mixed in the question.

4 answers

10

It is possible to create a immutable object in Javascript through the method Object.freeze, and a immutable reference through the method 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 to prevent an object from being inherited, I don’t know any way of doing that, and I don’t think it’s possible at all.

Classical vs. Prototypical OO

Notice you asked about classes, but I gave my answer by just talking about objects. Why? Simply because, strictly speaking, Javascript does not have the concept of "classes".

In 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 a single class. Class inherits from class, so that instances of the specific class have similar structure and behavior to instances of the general class.

In the 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 of the existing object (here called prototype), modifying what you want and keeping (sharing) the rest. There are no classes, only construction functions.

Javascript heritage

For historical reasons, although conceptually Javascript is a language that follows the prototypical OO, its syntax attempts to "hide" the fact - making it more similar to the classical one. The result is a "salad", as shown 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 the prototype of the second). However, Javascript "hides" this simple relation, forcing us to create a constructor method, assign it the property prototype and invoke it through the keyword new.

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

Probably due to this fact (from the constructor define everything about the object - both the initial attributes, placed in the constructor body through this.atr = val, as for the prototype, the one from whom the object will inherit) people confuse it with the "class" of the object. And for the convenience that this constructor method offers, rarely does anyone use [explicitly] prototypic inheritance in practice, so much so that there are plans to introduce the concepts of classical OO in future versions of Javascript. Maybe someday, what you ask is actually possible.

Completion

Since there are no Javascript classes, it makes no sense to talk about "static classes" (not even "static attributes" or "class attributes"). If you want to expose a collection of attributes and methods using a given name, it is most natural to do so 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 explained, but you cannot prevent other objects from inheriting from it: anyone can create a new constructor function, assign StaticTest as his prototype and call this function - producing objects they inherit from StaticTest.

  • Maybe I didn’t explain myself correctly, but the idea of the question is to know if there is a way to prevent the execution of the "new" (i.e., prevent the instantiation of new objects). Still, +1 for the information on how to make it immutable. :)

  • Another thing: an abstract class (in Java, for example) does not prevent your inheritance. When I asked the question, I did not have this concern in mind either. But again, thanks for the detailed explanations.

  • @Luizvieira to prevent the execution of "new" I believe that the best that can be done is to throw an exception in the constructor if the "Singleton" instance already exists. And it’s easy to inherit from Singleton, as explained in the answer, so if that’s not a concern for you, so much the better!

  • 1

    In this case, using a literal object or throwing an exception in the constructor gives in the same (new MeuLiteral also throws exception, because the object is not a function).

8

There is serious confusion here about concepts of classes and members (attributes) in the question.

Static attributes

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

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

Immutable, invariant or constant objects

We mustn’t confuse static with immutable. In Java, for example, an immutable attribute or variable is declared with final and not with static.

In javascript, it seems that the best option is the Object.freeze, as seen in @mgibsonbr’s reply.

Visibility

The question cited the question of preventing the external instantiation of the class. In languages like Java, this is obtained by creating a constructor private. See, again, that this has nothing to do with the concepts of static or immutable.

Completion

To achieve your goal, use a single configuration, my suggestion is simply to use a global variable.

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

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

  • In Java there is also the clause 'Static', preventing the instantiation of the class without necessarily having to be the private or protected constructor.

  • 4

    "don’t spend too much time protecting your code from yourself" excellent advice, one thing is you make accidental errors harder (by yourself or by your team), another is wanting to make the code "idiot-proof". There are pro limits that can be done to protect the code (even when it comes to Apis), it’s best to do what seems reasonable and leave the rest loose (i.e. I agree with your suggestion to use a global, optionally "freezing" object and/or reference to avoid accidental modifications, and not to worry about subclasses).

  • True, again +1 for the great suggestions. :)

  • @Luizvieira I haven’t touched Java in a while, is this a new feature of the language? (V6 forward) I know the classes internal static - which has nothing to do with restrictions on instantiation - but nothing like what you are describing. Are there any reminders you could refer me to? I’m curious...

  • @Luizvieira Maybe you’re confusing it with abstract.

  • 1

    @mgibsonbr Um, sorry, you’re right. The Java way to make the class static is to make all attributes and methods static. There is the 'Abstract' clause, which also prevents instantiation.

  • @utluiz Exactly. :) Please, sorry.

  • 2

    I edited the question to replace "static" with "abstract". I think it makes more sense, even though the answers have considered the intention to make static classes.

  • 1

    @mgibsonbr: it is in C# that you can have static classes.

  • 1

    ... however, don’t think so that making them classes was a good idea...

Show 5 more comments

6


In Javascript there are no static classes, properties or methods. 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 define StaticTest, then new StaticTest() will generate a Typeerror. It is a way to solve the problem (with try..catch around the line that generates the exception).

Another way is to change the return of the constructor. I don’t understand exactly what you want in the test, but it is possible to return the object itself 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 (such as false), the builder will return this, that is, the newly created instance.


Considering its edition that exchanges "static" for "abstract", I think the best output would be to even use a literal object, since the intention is to avoid instantiation:

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

Thus, StaticTest would generate an exception, which you can capture with try..catch:

try {
    new StaticTest();
} catch(e) {
    // trate a exceção aqui
}
  • 2

    Return own StaticTest is a good way to simulate a Singleton. To simulate an abstract class (i.e. not instantiable), perhaps the ideal would be to make an exception in the constructor. This does not in fact prevent an inheritance from occurring, as I have detailed in my reply, but I believe it is close to what the OP wants (lacking only immutability). P.S. +1 by "if you try to return a value that is not object (...) will return this", did not know this detail, easy to go unnoticed...

  • Several nice answers (and very difficult to choose one), but I chose this for the simple and direct suggestion of using a literal object indicating that generates a Typeerror (and, in the end, it is the same conclusion as the other answers). : ) Thank you all!

3

One way to build an abstract "class" with the usual inheritance of Javascript prototypes is by using a constructor like the following:

/**
 * 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 Czech constructor if it was called directing (as in new Base()) with the property constructor, and makes an exception if it 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 constructor of the base class with apply, and creates its prototype based on 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 more extensive example can be found here.

Note the use of Jsdoc @abstract.

  • If I understand this correctly, it only makes sense if there needs to be some logic in the constructor, right? Otherwise, the effect is the same as using a literal object (which throws exception in instantiation attempts, and can be extended).

  • 1

    @bfavaretto: not only that, this solution also maintains consistency between the "classes". All are coded in a similar way, with constructors and prototypes. And of course, even if you don’t have logic in the Base, you can add it later without having to change Derived.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.