Assume we have:
class FinalClass {
...
}
How to modify it to make
class WrongClass extends FinalClass {
...
}
or
new WrongClass(...)
to generate an exception? Perhaps the most obvious solution is to do the following in the FinalClass's constructor:
if (this.constructor !== FinalClass) {
throw new Error('Subclassing is not allowed');
}
Does anyone have a more cleaner solution instead of repeating these lines in each class that supposed to be final (probably with a decorator)?
Assume we have:
class FinalClass {
...
}
How to modify it to make
class WrongClass extends FinalClass {
...
}
or
new WrongClass(...)
to generate an exception? Perhaps the most obvious solution is to do the following in the FinalClass's constructor:
if (this.constructor !== FinalClass) {
throw new Error('Subclassing is not allowed');
}
Does anyone have a more cleaner solution instead of repeating these lines in each class that supposed to be final (probably with a decorator)?
Share Improve this question edited Oct 23, 2016 at 23:52 Felix Kling 816k180 gold badges1.1k silver badges1.2k bronze badges asked Aug 3, 2016 at 15:50 Dmitry DruganovDmitry Druganov 2,3583 gold badges24 silver badges33 bronze badges 7 | Show 2 more comments3 Answers
Reset to default 15Inspect this.constructor
in the constructor of FinalClass
and throw if it is not itself. (Borrowing inspection of the this.constructor
instead of this.constructor.name
from @Patrick Roberts.)
class FinalClass {
constructor () {
if (this.constructor !== FinalClass) {
throw new Error('Subclassing is not allowed')
}
console.log('Hooray!')
}
}
class WrongClass extends FinalClass {}
new FinalClass() //=> Hooray!
new WrongClass() //=> Uncaught Error: Subclassing is not allowed
Alternatively, with support, use new.target
. Thanks @loganfsmyth.
class FinalClass {
constructor () {
if (new.target !== FinalClass) {
throw new Error('Subclassing is not allowed')
}
console.log('Hooray!')
}
}
class WrongClass extends FinalClass {}
new FinalClass() //=> Hooray!
new WrongClass() //=> Uncaught Error: Subclassing is not allowed
______
As you say, you could also achieve this behaviour with a decorator.
function final () {
return (target) => class {
constructor () {
if (this.constructor !== target) {
throw new Error('Subclassing is not allowed')
}
}
}
}
const Final = final(class A {})()
class B extends Final {}
new B() //=> Uncaught Error: Subclassing is not allowed
As Patrick Roberts shared in the comments the decorator syntax @final
is still in proposal. It is available with Babel and babel-plugin-transform-decorators-legacy.
constructor.name
is easy enough to spoof. Just make the subclass the same name as the superclass:
class FinalClass {
constructor () {
if (this.constructor.name !== 'FinalClass') {
throw new Error('Subclassing is not allowed')
}
console.log('Hooray!')
}
}
const OopsClass = FinalClass
;(function () {
class FinalClass extends OopsClass {}
const WrongClass = FinalClass
new OopsClass //=> Hooray!
new WrongClass //=> Hooray!
}())
Better to check the constructor
itself:
class FinalClass {
constructor () {
if (this.constructor !== FinalClass) {
throw new Error('Subclassing is not allowed')
}
console.log('Hooray!')
}
}
const OopsClass = FinalClass
;(function () {
class FinalClass extends OopsClass {}
const WrongClass = FinalClass
new OopsClass //=> Hooray!
new WrongClass //=> Uncaught Error: Subclassing is not allowed
}())
Assuming you are using TypeScript, the simpliest way of achieving this is to set the constructor to private, as in:
class MyClass {
private constructor() {}
}
Then, you should get a compile-time error if you try to instantiate, or extend, this class.
final
ed as a premature performance hack. – Jared Smith Commented Aug 3, 2016 at 16:07Object.freeze
can be used,Object.defineProperty
, etc., recursively even if need be (to 'deep freeze'). I just don't know why you would, in general, want to do that to a class other than performance and it would have to be one hell of a perf improvement. – Jared Smith Commented Aug 3, 2016 at 16:09