🐕

一日一処: Javaなどにあるメソッドシグネチャ(オーバーロード)がTypeScriptでどう実現されるのか

2024/02/28に公開

メソッドシグネチャ

Javaなどいくつかの言語では、メソッド名と引数が異なる場合は、複数同じものが定義できる仕組みがある。たとえば、Javaではこのようなコードだ。

public class Main {
  public static void main(String[] args) {
    Main.say(); // hello
    Main.say("world"); // world
  }
  public static void say() {
    Main.say("hello");
  }
  public static void say(String message) {
    System.out.println(message);
  }
}

同じ名称のメソッド名が定義されているが、これはエラーにならない。mainメソッドで実行をしているが、渡されている引数によってメソッドが定まり、それぞれ実行される。非常に便利だが混乱も招きやすい。扱いには注意が必要だ。

JavaScriptでの同名メソッド定義

同様のコードをJavaScriptで再現した。

class Main {
  say() {
    this.say("hello")
  }
  say(message) {
    console.log(message)
  }
}

const main = new Main()
main.say() // undefined
main.say('world') // world

エラーにはならないが、このような結果になる。一つ目の引数なしのsayメソッドでは、引数が与えられないため、undefinedとなった。本来は、引数なしで定義したメソッドが実行されてほしいが、うまくいかない。これは、あとに記述されたメソッドがその前のものを上書きしているからだ。平たく言うと、JavaScriptにはメソッドのオーバーロード機能が存在しないため、このような結果となる。

TypeScriptではどうなるのか

JavaScriptを基準として作られているTypeScriptも当然できないと考えられるが、VisualStudioなどのエディターでは、それぞれのメソッドの引数に応じた複数のドキュメントが表示されることがある。JavaScriptに存在しないオーバーロードをTypeScriptではどのように実現しているのか。

interface Main {
    say(): void
    say(message: string): void
}
class Main {
    say(message?: string) {
        console.log(message ?? 'hello')
    }
}
const main = new Main()
main.say()
main.say("world")

このようになる。メソッドの定義は、Javaのように複数行うのではなく、型宣言の方を複数増やすことで、擬似的にオーバーロードを再現することとなる。これが一番うれしいのは、型の宣言を行うことで、メソッドの機能を明確にすることができる点だ。特に、引数が多くなってしまうと、どれが必須なのかわからなくなる。また、この型宣言を適切に活用できれば、メソッドの引数の順番を状況に応じて、変更することも可能だ。

interface Main {
    say(name: string): void
    say(name: string, message: string): void
    say(name: string, age: number, message: string): void
}
class Main implements Main {
    say(name: string, value1?: number|string, value2?: string) {
        let age, message
        if (typeof value1 === 'string') {
            message = value1
        } else {
            age = value1
            message = value2
        }
        if (typeof value1 === 'number') {
            age = value1
        }
        console.log(
            `hello ${name}`
            + (message ? `, ${message}` : '')
            + (age ? `, ${age}yo` : '')
        )
    }
}

const main = new Main()
main.say('Bob')
// hello Bob
main.say('Alice', 'Congratulations!')
// hello Alice, Congratulations!
main.say('John', 20, 'happy birthday')
// hello John, happy birthday, 20yo

便利ではあるが、複雑になると、それだけデータ型の再確認が必要となるので、こうなると、JavaScriptと殆ど変わらず、TypeScriptの恩恵は受けられないように思える。
ただ、一定の使い方では、便利な場面もあると思うので、積極的に使うことを検討するとよいだろう。

Discussion