TypeScript でパラレル継承階層

以前に、パラレル継承階層を実装するとダウンキャストが避けられないとの記事を書きました。 またTypeScript を使うとある程度対応できる記事を書きました。

そして、ダウンキャストをなくすためには下記の2つの機能が言語に必要となりますが、Scala にはその機能があることがわかりました。

  • 継承可能な型引数をサポートすること
  • ジェネリックワイルドカードをサポートすること

Scalaを利用すれば完全に対応できることがわかりました。

中身と入れ物の関係がパラレル継承階層になりがちなので、TypeScriptと同じ例を下記に示しました。

中身は BaseContentDerivedContentDerivedContent2 と継承します。一方、入れ物はこれに対応して、BaseBoxDerivedBoxDerivedBox2 と継承します。

BaseBoxは中身を参照するcontentプロパティを持ち、これをTContent型としています。入れ物の継承に従ってこの型引数も制約の型をより継承した中身の型へと変えていきます。このようにすることで、contentへの代入は型安全が保たれます。また、サンプルプログラムでは省略していますが、各入れ物クラスの中でcontentのハンドリングの際にダウンキャストは発生しません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class BaseContent {
}

class BaseBox {
type TContent <: BaseContent
var content: Option[TContent] = None
}

class DerivedContent extends BaseContent {
val prop1 = 0
}

class DerivedBox extends BaseBox {
override type TContent <: DerivedContent
}

class DerivedContent2 extends DerivedContent {
val prop2 = 0
}

class DerivedBox2 extends DerivedBox {
override type TContent = DerivedContent2
}

object ParallelInheritance
{
def main(args: Array[String]) = {
val derivedBox = new DerivedBox()
//derivedBox.content = Some(new DerivedContent())
// derivedBox.content = Some(new BaseContent()) //Found: BaseContent Required: DerivedContent 想定通りのエラー
val derivedBox2 = new DerivedBox2()
val baseBox: BaseBox = derivedBox2
//baseBox.content = Some(new BaseContent()) //error: type mismatch found: BaseContent required: baseBox.TContent 想定通りのエラー
println(derivedBox2.content.get.prop1)
derivedBox2.content = Some(new DerivedContent2())
// derivedBox2.content = Some(new DerivedContent()) //Found: DerivedContent Required: DerivedContent2 想定通りのエラー
// derivedBox2.content = Some(new BaseContent()) //Found: BaseContent Required: DerivedContent2 想定通りのエラー
}
}
参照