2

Today I came up with an idea, to use Proxy object, to allow creation of multiple abstract classes, for example

const abstract = abstractClass => new Proxy(abstractClass,{
  construct: (target, args) => { throw new Error('Error, class was declared as abstract!') }
});

class A{}
class B{}
class C{}
[A,B,C] = [A,B,C].map(c => abstract(c));

So my question is whether this solution has any drawbacks, and if so, what they are.

1
  • 3
    I think that abstract classes should be part of compilation, and not runtime. You should check typescript abstract classes. Commented Jul 26, 2017 at 20:13

1 Answer 1

5

The drawback is that Proxy cannot be reliably polyfilled in ES5 and it is slow.

A good recipe is to solve this with inheritance:

class Abstract {
    constructor() {
        if (Object.getPrototypeOf(this.constructor) === Abstract
            || this.constructor === Abstract) {
            throw new Error("Cannot instantiate abstract class");
        }
    }
};

class AbstractFoo extends Abstract {...}

class ConcreteFoo extends AbstractFoo {...}

Abstract and AbstractFoo will throw an error when instantiated directly.

ES.next decorators can be used to make the task easier:

function abstract(target) {
    return class Abstract extends target {
        constructor(...args) {
            super(...args);
            if (this.constructor === Abstract) {
                throw new Error("Cannot instantiate abstract class");
            }
        }
    };
}
@abstract
class AbstractFoo {...}

class ConcreteFoo extends AbstractFoo {...}

Since decorators are basically helper functions, they can be applied directly in plain ES6:

const AbstractFoo = abstract(class AbstractFoo {...});

class ConcreteFoo extends AbstractFoo {...}

Notice that in the case of a decorator original constructor will be evaluated before throwing an error, this is the price of using extends. The workaround is to use a function instead of class and inherit it from target manually, similarly to TypeScript __extends and Babel __inherits helper functions, this way an error could be thrown before super(). Another workaround is to use new.target (currently cannot be transpiled to ES5 with Babel):

function abstract(target) {
    return class Abstract extends target {
        constructor(...args) {
            if (new.target === Abstract) {
                throw new Error("Cannot instantiate abstract class");
            }
            super(...args);
        }
    };
}

A cleaner way is to use TypeScript (as it was suggested in comments), it introduces abstract classes as language feature and takes care of it at design time. This allows to not pollute prototype chain.

Sign up to request clarification or add additional context in comments.

5 Comments

Babel support isn't quite there yet, but you can also use new.target === Abstract to check instead, which can be used before super(...)
@loganfsmyth I didn't mention new.target exactly because of that, but yes, you are right, in native ES6 it's the way to go.
Fair. The question never mentions Babel though, so if you're on Node it's perfectly fine.
@loganfsmyth Babel 7 will support new.target - at least transpiling it into this.constructor.
Yup! Just not out of alpha yet.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.