TypeScript - ジェネリッククラス

こんにちは、未来のプログラミングスーパースターたち!今日は、TypeScriptのジェネリッククラスの面白い世界に飛び込みます。プログラミングが新しい方也不用担心;私はこの旅をステップバイステップで案内します。これまでに数多くの学生たちを指導してきましたように。お気に入りの飲み物を用意して、リラックスして、一緒にこの冒険を楽しんでいきましょう!

TypeScript - Generic Classes

ジェネリッククラス

ジェネリッククラスとは?

アイスクリーム屋に行っていると、フレーバーを選ぶ代わりにデータタイプを選んでいると想像してみてください。それがジェネリッククラスの本質です!ジェネリッククラスは、データの安全を犠牲にすることなく、異なるデータタイプで動作する柔軟で再利用可能なコンポーネントを作成することを可能にします。

簡単な例から始めましょう:

class Box<T> {
private content: T;

constructor(value: T) {
this.content = value;
}

getValue(): T {
return this.content;
}
}

この例では、Boxはジェネリッククラスです。<T>は後で指定するタイプのプレースホルダーです。アイスクリーム屋に「注文する時にフレーバーを決める」と言っているようなものです。

以下に分解します:

  • class Box<T>: ジェネリッククラスBoxを宣言し、タイプパラメータTを設定します。
  • private content: T: contentは型Tになります。
  • constructor(value: T): コンストラクターは型Tの値を受け取ります。
  • getValue(): T: このメソッドは型Tの値を返します。

このクラスの使用方法を見てみましょう:

let numberBox = new Box<number>(42);
console.log(numberBox.getValue()); // 出力: 42

let stringBox = new Box<string>("Hello, TypeScript!");
console.log(stringBox.getValue()); // 出力: Hello, TypeScript!

素晴らしいですね!同じBoxクラスを使って数値と文字列の両方を保存しました。魔法の箱のように何でも収納できるけれども、収納しているものの型を正確に覚えています!

複数のタイプパラメータ

時々、一つのタイプパラメータでは不十分です。複数のタイプパラメータを持つもっと複雑な例を作ってみましょう:

class Pair<T, U> {
private first: T;
private second: U;

constructor(first: T, second: U) {
this.first = first;
this.second = second;
}

getFirst(): T {
return this.first;
}

getSecond(): U {
return this.second;
}
}

このPairクラスは、潜在的に異なる型の二つの値を保持できます。アイスクリームのツインコーンのように、それぞれのスcoopが異なるフレーバーを持つことができます!

Pairクラスを使ってみましょう:

let pair = new Pair<string, number>("Age", 30);
console.log(pair.getFirst());  // 出力: Age
console.log(pair.getSecond()); // 出力: 30

ジェネリックの制約

時々、ジェネリッククラスで使用できる型を制限したいときがあります。制約を使ってこれを行うことができます。例えば、「スパイシーすぎないフレーバーを全部に」と言っているようなものです。

interface Lengthwise {
length: number;
}

class LengthChecker<T extends Lengthwise> {
checkLength(obj: T): string {
return `The length is: ${obj.length}`;
}
}

この例では、T extends LengthwiseTlengthプロパティを持つ型であることを意味します。使い方を見てみましょう:

let stringChecker = new LengthChecker<string>();
console.log(stringChecker.checkLength("Hello")); // 出力: The length is: 5

let arrayChecker = new LengthChecker<number[]>();
console.log(arrayChecker.checkLength([1, 2, 3])); // 出力: The length is: 3

// これはエラーを引き起こします:
// let numberChecker = new LengthChecker<number>();
// Type 'number' does not satisfy the constraint 'Lengthwise'.

ジェネリックインターフェースとジェネリッククラスの実装

今度は、ジェネリックインターフェースを実装するジェネリッククラスにスキルをさらに進めてみましょう。異なる型のアイスクリーム(クラス)のためのレシピ(インターフェース)を作成することのようなものです!

まず、ジェネリックインターフェースを定義します:

interface Repository<T> {
getById(id: number): T;
save(item: T): void;
}

このRepositoryインターフェースは、データの保存と检索を処理するクラスの契約を定義します。これを実装するジェネリッククラスを作成しましょう:

class GenericRepository<T> implements Repository<T> {
private items: T[] = [];

getById(id: number): T {
return this.items[id];
}

save(item: T): void {
this.items.push(item);
}
}

GenericRepositoryクラスはRepositoryインターフェースを実装し、任意の型Tで動作できます。使い方を見てみましょう:

interface User {
name: string;
age: number;
}

let userRepo = new GenericRepository<User>();

userRepo.save({ name: "Alice", age: 30 });
userRepo.save({ name: "Bob", age: 25 });

console.log(userRepo.getById(0)); // 出力: { name: "Alice", age: 30 }
console.log(userRepo.getById(1)); // 出力: { name: "Bob", age: 25 }

この例では、Userオブジェクトのためのリポジトリを作成しました。しかし、私たちのジェネリック実装の美しさは、他の任意の型のためにも簡単にリポジトリを作成できることです!

メソッド表

ここで、カバーしたメソッドの摘要を示す便利な表を示します:

メソッド 説明
constructor(value: T) ジェネリッククラスの新しいインスタンスを作成 new Box<number>(42)
getValue(): T ジェネリッククラスに保存された値を返す numberBox.getValue()
getFirst(): T ペアの最初の値を返す pair.getFirst()
getSecond(): U ペアの二つ目の値を返す pair.getSecond()
checkLength(obj: T): string オブジェクトの長さをチェック(制約付き) stringChecker.checkLength("Hello")
getById(id: number): T リポジトリからIDでアイテムを取得 userRepo.getById(0)
save(item: T): void リポジトリにアイテムを保存 userRepo.save({ name: "Alice", age: 30 })

そして、ここまでがTypeScriptのジェネリッククラスの旅です。基本的なボックスから複雑なリポジトリまで、さまざまなことを学びました。実践は完璧を生みますので、これらのコンセプトを試してみてください。あなたが次の大きなことを創造するかもしれません!次回まで、ハッピーコーディングを!

Credits: Image by storyset