海未「それではインターフェイスの話を始めましょう」
穂乃果「あれ、意外と元気だ」
ことり「海未ちゃんね、だんだん責めても動じなくなってきて・・・」
穂乃果「あー、毎度のことで順応してきたんだ・・・」
海未「インターフェイスという概念は、JavaやC#を知っている人にとっては馴染みのあるものでしょう。実装を持たない型定義です」
穂乃果「実装を持たない・・・型定義・・・?」
海未「まず、このコードを見てください」
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 40 41 42 43 44 45 46 |
class SchoolIdol { constructor( name: string, age: number, school: string ) {} sing() { console.log('Singing!!'); } dance() { console.log('Dancing!!'); } } class ProfessionalIdol { constructor( name: string, age: number, production: string ) {} sing() { console.log('Singing!!'); } talk() { console.log('Talking!!') } } class Alpaca { constructor( age: number ) {} bleat() { console.log('meeeeeeeeeeeeeeee'); } } var honoka = new SchoolIdol('Honoka Kosaka', 16, 'Otonokizaka High'); var uzuki = new ProfessionalIdol('Uziki Shimamura', 17, '346Pro'); var alpaca = new Alpaca(10); |
海未「似たようなコードは何度も出てきた気がしますが、クラスが3つ普通に定義されていますね」
ことり「アルパカさん10歳だったの?」
海未「・・・適当です。さて、ここでアイドルフェス的なものが開催されることになり、出演者を出演順に配列に入れていくとしましょう。ところが・・・」
1 2 |
var casts: SchoolIdol[] = [honoka, uzuki]; |
海未「例えばこう書くと、コンパイルエラーになります。uzukiはSchoolIdolではないからですね」
ことり「じゃあ、var casts: ProfessionalIdol[] = [honoka, uzuki]
もだめだよね」
穂乃果「だからってany[]
にしちゃうと、フェスにアルパカが入り込んじゃうよね」
海未「そうです。この場合、以前見たようにSchoolIdolとProfessionalIdolが同じIdolクラスを継承している、といった構成になっていれば問題はないのですが、今回は別の方法をとりましょう」
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
interface Singable { sing(): void; } class SchoolIdol implements Singable { constructor( name: string, age: number, school: string ) {} sing() { console.log('Singing!!'); } dance() { console.log('Dancing!!'); } } class ProfessionalIdol implements Singable { constructor( name: string, age: number, production: string ) {} sing() { console.log('Singing!!'); } talk() { console.log('Talking!!') } } class Alpaca { constructor( age: number ) {} bleat() { console.log('meeeeeeeeeeeeeeee'); } } var honoka = new SchoolIdol('Honoka Kosaka', 16, 'Otonokizaka High'); var uzuki = new ProfessionalIdol('Uziki Shimamura', 17, '346Pro'); var alpaca = new Alpaca(10); var casts: Singable[] = [honoka, uzuki]; for (let cast of casts) { cast.sing(); } |
海未「Singable
というインターフェイスを用意して、SchoolIdolとProfessionalIdolはそれをimplements
することにしましょう。こうすると、SchoolIdolとProfessionalIdolをどちらもSingable型として同じ扱いができるようになります」
穂乃果「歌える人だけ出場権があるんだ」
海未「Singableインターフェイスではsing(): void
という宣言がありますが、これはSingableインターフェイスを実装するクラスは必ずsingメソッドを持っていなくてはならないということです」
ことり「だからコードの最後で、キャスト全員のsingを呼び出したりできるんだね」
海未「この例では配列について見ましたが、同様に高階関数の引数宣言にも適用できます。また、クラスの継承関係に関係なく実装できますから、継承構造をまたがってグルーピングをしたい場合に便利ですね」
海未「最後に、これをインターフェイスなしに実現する方法を紹介します。構造的部分型という仕組みです」
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
class SchoolIdol { constructor( name: string, age: number, school: string ) {} sing() { console.log('Singing!!'); } dance() { console.log('Dancing!!'); } } class ProfessionalIdol { constructor( name: string, age: number, production: string ) {} sing() { console.log('Singing!!'); } dance() { console.log('Dancing!!'); } talk() { console.log('Talking!!'); } } class Alpaca { constructor( age: number ) {} bleat() { console.log('meeeeeeeeeeeeeeee'); } } var honoka = new SchoolIdol('Honoka Kosaka', 16, 'Otonokizaka High'); var uzuki = new ProfessionalIdol('Uziki Shimamura', 17, '346Pro'); var alpaca = new Alpaca(10); var casts: SchoolIdol[] = [honoka, uzuki]; for (let cast of casts) { cast.sing(); } |
海未「ProfessionalIdolにdanceメソッドを追加しました。これによって、ProfessionalIdolはSchoolIdolと同じsingとdanceの各メソッドを持っていることになります」
穂乃果「ふむふむ」
海未「そうすると、SchoolIdol型が要求されるところに代わりにProfessionalIdolを渡すことができるようになります」
ことり「たしかに、SchoolIdolの配列に卯月ちゃん入れてるけど・・・どうしてなのかな」
海未「ProfessionalIdolはSchoolIdolと同じことができるから、ですね。singもdanceもできますから外から見た振る舞いは同じですし、実際JavaScriptでは同じことができました」
穂乃果「ダックタイピングだね」
ことり「ちゅんちゅん」
海未「次回はジェネリクスを紹介します。それで、TypeScriptの主な機能は一通りですね」