Python なら、クラスメソッド内から自分のクラスを第一引数で参照することができる。
クラスメソッドを通じて自分自身のインスタンスを提供する手段はプログラミングではよくある。
たとえば datetime.date.today() は datetime.date のインスタンスを生成する。
date(year, month, day) と初期化する作業を、肩代わりしてクラスメソッドが行って返却している。
Python ではデフォルトで第一引数が呼び出し元になっており、
メソッドでは self 、クラスメソッドでは cls という変数名をつける慣習になっている。
ES2015 には static という Python の @staticmethod デコレータに相当するキーワードは存在するが、 @classmethod に相当するキーワードがない。
ではどうやるか という話。 (自分でもこのやり方で合っているか自信がない)
試した環境は NodeJS v6.1.0 。既に V8 でサポートされているので特に babel を経由していない。
まず Python のコード例を書く。
Python での例
class X(object):
@classmethod
def create(cls):
return cls()
class Y(X):
pass
x = X.create()
y = Y.create()
print(isinstance(x, X))
print(isinstance(y, X))
print(isinstance(y, Y))
True
True
True
JavaScript (ES2015) での失敗例
class X {
static create() {
return new X();
}
}
class Y extends X {};
const x = X.create();
const y = Y.create();
console.log(x instanceof X);
console.log(y instanceof X);
console.log(y instanceof Y);
true
true
false
X 内で new X(); がハードコードされてしまっているため、
継承した Y の create() により生成されるインスタンスである y までもが、
実際には Y ではなく単なる X になってしまっている。
これは全く望み通りではない。
JavaScript (ES2015) での成功例 (冗長 ver)
冗長だがシンプルに、新しく static メソッドを再定義する戦略をやってみる。
class X {
static create() {
return new this();
}
}
class Y extends X {
static create() {
return new Y();
}
}
const x = X.create();
const y = Y.create();
console.log(x instanceof X);
console.log(y instanceof X);
console.log(y instanceof Y);
true
true
true
ちゃんと望み通りの結果になっている。
y は Y のインスタンスであるし、Y は X を継承している。
しかし、これでは継承するたびに全てのクラスメソッドを再定義する必要がある。
そのために、元のクラスでの定義を常に把握しておかなくてはならなくなってしまう。
不便だ。
JavaScript (ES2015) での成功例
そこで、いろいろやっているうちに this を思い出した。
Python での self が this に相当するなら、同じ関係がクラスメソッドでも成り立つのではないか?
なるほど、クラスから生えている static なメソッドをドット演算子で呼んだら、 this はそのクラスになる……。
というわけでやってみた。
class X {
static create() {
return new this();
}
}
class Y extends X {};
const x = X.create();
const y = Y.create();
console.log(x instanceof X);
console.log(y instanceof X);
console.log(y instanceof Y);
true
true
true
目論見通り。
y は Y のインスタンスであるし、Y は X を継承している。
ハードコードされていないので Y の create() を再定義する必要がない。