ガチガチに固めた、静的な言語のようなクラスをJavaScript(TypeScript+ES6+ES7)で作ってみるシリーズ。
継承不可なクラス
「継承しないといけないクラス」は抽象クラスとしてTypeScript1.6から使えるようになった。が、逆の「継承してはいけないクラス」を定義することができない。
継承されるとまずいクラスがある場合、運用でカバーするしかないが、人間やるなと言われるとやりたくなるもので、運用でなんとかする方法論はだいたいうまくいかない。
/**
* いいか?おまえら。絶対継承するなよ?継承するなよ?
*/
class DontInherit{}
/**
* 「絶対○○するな」=「やれ」ということだな!
*/
class Oyakusoku extends DontInherit{}
/**
 * やるなっていっただろー!?
 */
var ostrich = new Oyakusoku();
console.log(ostrich instanceof DontInherit);	//true
@final decorator
Javaのfinal classやC#のsealed classはそのような継承不可なクラスを定義するものだ。そこで@finalというデコレータをつくってみた。このデコレータを使うとクラスを継承しようとするとランタイムエラーが発生する。
@final class DontInherit{}
class Oyakusoku extends DontInherit{}
var ostrich = new Oyakusoku();	//runtime error! : A class "Oyakusoku" cannot inherit from @final class "DontInherit".
仕組み
仕組みは簡単で、コンストラクタ内の処理で基底クラス(のコンストラクタ関数)名とthisオブジェクトのクラス(のコンストラクタ関数)名を比較して違っていたらエラーにするだけ。
const base = target;	//targetは基底クラスのコンストラクタ
//中略
//thisのprototype経由でコンストラクタ名を取得
const thisname = Object.getPrototypeOf(this).constructor.name;
//名前が違っていたらエラーをthrow
if(thisname!==base.name){
	throw Error(`A class "${thisname}" cannot inherit from @final class "${base.name}".`);
}
ES6(ES2015)から導入されたクラスだが、直接クラス名を取得する方法は無い.nameプロパティで仕様では取得できるが、多くの環境で非対応。ただし、ES6のクラスはシンタックスシュガーでしかないので、コンストラクタの関数名prototype.constructor.nameを取得することでクラス名を得る事ができる。
制限
名前で比較するので基底クラスに名前が無いと機能しない。もっとも、匿名クラスにデコレータはつけられない様子。
@final	//error!: Decorators are not valid here
var AnonymousClass = class {}
なお、残念ながらbabelでは今回のコードは動かない。
コード
githubに今回作った@finalデコレータを公開している。