はじめに
この記事は、前回書いたこちらの記事: ポリモーフィズムをわかった気になろう! の「【補足】TypeScriptでのオーバーロードについて」に関する補足記事となります。
上記の記事では「オーバーロード」によってポリモーフィズムを実現する例として、TypeScriptを用いたオーバーロードを紹介しました。
この記事では、TypeScriptにおいてオーバーロードを使わずによりシンプルな方法でポリモーフィズムを実現する方法として、複数の型を同時にサポートできる「union型」と「ジェネリクス」を、簡単な具体例とともに説明したいと思います。
(再掲)オーバーロードの例
オーバーロードは、同じクラス内で同じ名前のメソッドを、異なる引数の "型" や "数" で定義することにより、同じメソッド名を使用していながら異なる引数の組み合わせに対応する手法のことでした。 以下の例では、静的型付け言語であるTypeScriptを用いて説明しています。 【具体例】(add関数が異なる引数の型と数に対応しています)// シグネチャの定義(引数と戻り値の型を定義している)
function add(a: number, b: number): number;
function add(a: string, b: string): string;
// 実装部分(引数が整数の場合は整数を、文字列の場合は文字列を返す。その他の型の場合はエラー。)
function add(a: number | string, b: number | string): number | string {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
} else if (typeof a === 'string' && typeof b === 'string') {
return a.concat(b);
} else {
throw new Error("Invalid argument types");
}
}
const result1 = add(1, 2); // => 3 (number)
const result2 = add("Hello", " World"); // => "Hello World" (string)
上記の例では、add関数がオーバーロードされており、2つの引数が数値の場合は数値として加算され、文字列の場合は文字列として連結されます。このように、オーバーロードは異なる引数の型や数に対応する同じ名前の関数を定義することで、 ポリモーフィズムを実現しています。
union型
union型とは、プログラミング言語(特にTypeScript)で使用される型システムの概念で、複数の型のいずれかを表すことができます。
union型を使うことで、関数や変数が受け付けるデータ型を複数の型の中から1つに限定せず、いくつかの型から選択できるようになります。
例えば、ある関数が整数と文字列の両方を引数に取ることができる場合、その関数の引数の型を number | string として定義できます。このように、|(パイプ)を使って複数の型を組み合わせることで、それらの型のいずれかを表すunion型を作成できます。
以下はオーバーロードの例と同じ振る舞いを持つ関数を、union型を使って実装した例です。
function add(a: number | string, b: number | string): number | string {
if (typeof a === "number" && typeof b === "number") {
return a + b;
} else if (typeof a === "string" && typeof b === "string") {
return a.concat(b);
} else {
throw new Error("Invalid argument types");
}
}
console.log(add(1, 2)); // 出力: 3
console.log(add("Hello, ", "World!")); // 出力: Hello, World!
この例では引数の型がnumber | stringとして定義されており、numberまたはstringのいずれかの型が許容されます。関数内部では、typeof演算子を使って引数の型をチェックし、適切な処理を行っています。
このようにunion型を使うことで、オーバーロードを使った実装よりもシンプルに書くことができます。
ジェネリクス(総称)
ジェネリクスとは、型をパラメータとして渡すことができる機能です。
ジェネリクスを使用することで、1つの関数やクラスで複数の型に対応することができ、コードの再利用性が向上します。
ジェネリクス型は通常、大文字の "T" などで表されます。
以下にTypeScriptでジェネリクスを使用する例を示します。
function identity<T>(arg: T): T {
return arg;
}
const numberValue: number = identity<number>(42);
const stringValue: string = identity<string>("Hello, world!");
console.log(numberValue); // 出力: 42
console.log(stringValue); // 出力: "Hello, world!"
この例では、identity関数がジェネリクス型 T を使用して定義されています。この関数は、型 T の引数 arg を受け取り、同じ型 T の値を返します。これにより、identity関数は、どのような型でも同じ振る舞いをすることができます。
関数を呼び出す時に、ジェネリクス型 T を具体的な型(例えば、numberやstring)に置き換えます。この例では、identity(42) と identity("Hello, world!") のように、関数を呼び出す際に型を指定しています。
まとめ
- union型は、複数の型のいずれかを表す型を定義することができます。これにより、関数やメソッドが異なる型の引数や戻り値を許容することができます。union型を使うことで、型の違いによる実装の重複を避けることができ、コードの再利用性を向上させることができます。
- ジェネリクスは、型をパラメータとして渡すことができる機能です。これにより、1つの関数やクラスで複数の型に対応することができます。ジェネリクスを使用することで、型の違いによる実装の重複を避けることができ、コードの再利用性を向上させることができます。
union型やジェネリクス型を用いることで、オーバーロードと同様に、異なる引数の型に対応することができ、結果としてポリモーフィズムを実現することができます。