🐷

Typescript(クラス)

2023/03/02に公開

ここでは、TypeScriptのクラスについて深ぼっていけたらと思います!

Typescriptは、クラスベースのオブジェクト指向プログラミングをサポートしています。クラスは、オブジェクトの設計図として機能し、同じクラスから複数のオブジェクトを作成することができます。この記事では、TypeScriptのクラスについて詳しく説明します。

クラスの定義

クラスを定義するには、classキーワードを使用します。以下は、クラスを定義する例です。

class Animal {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  sayName(): void {
    console.log(`My name is ${this.name}.`);
  }
}

この例では、Animalというクラスを定義しています。クラスには、nameとageというプロパティと、constructorとsayNameというメソッドがあります。constructorメソッドは、オブジェクトが作成されるときに自動的に呼び出されます。

クラスの継承

クラスは、他のクラスから継承することができます。継承を使用することで、既存のクラスを拡張して新しいクラスを作成することができます。以下は、クラスの継承を使用する例です。

class Dog extends Animal {
  breed: string;
  constructor(name: string, age: number, breed: string) {
    super(name, age);
    this.breed = breed;
  }
  sayBreed(): void {
    console.log(`My breed is ${this.breed}.`);
  }
}

この例では、Dogというクラスを定義しています。Dogクラスは、Animalクラスを継承しています。また、Dogクラスには、breedというプロパティと、sayBreedというメソッドがあります。constructorメソッドでは、superキーワードを使用して、親クラスのコンストラクターを呼び出しています。

アクセス修飾子

TypeScriptには、アクセス修飾子と呼ばれる機能があります。アクセス修飾子を使用することで、クラスのメンバーにアクセスする際の可視性を制御することができます。

以下は、アクセス修飾子を使用した例です。

class Person {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
  sayName(): void {
    console.log(`My name is ${this.name}.`);
  }
}

この例では、nameプロパティにprivate修飾子を使用して、プロパティに外部からアクセスできなくしています。これにより、Personクラスの外部からnameプロパティにアクセスすることはできません。ただし、sayNameメソッド内では、nameプロパティにアクセスすることができます。

TypeScriptには、他にも以下のようなアクセス修飾子があります。

'public': どこからでもアクセスできます(デフォルトの修飾子)。
'protected': 自身と子クラスからアクセスできます。
'readonly': 読み取り専用のプロパティに使用します。

getterとsetter

TypeScriptには、getterとsetterという機能があります。これらを使用することで、プロパティの値を取得したり、設定したりするためのカスタムメソッドを定義することができます。

以下は、getterとsetterを使用した例です。

class Rectangle {
  private _width: number;
  private _height: number;
  constructor(width: number, height: number) {
    this._width = width;
    this._height = height;
  }
  get width(): number {
    return this._width;
  }
  set width(value: number) {
    this._width = value;
  }
  get height(): number {
    return this._height;
  }
  set height(value: number) {
    this._height = value;
  }
  get area(): number {
    return this._width * this._height;
  }
}

この例では、Rectangleクラスを定義しています。Rectangleクラスには、widthとheightというプロパティと、areaという計算プロパティがあります。widthとheightプロパティには、getterとsetterを定義しています。また、areaプロパティには、getterを定義しています。

静的メンバー

TypeScriptには、静的メンバーと呼ばれる機能があります。静的メンバーは、クラスに固有のメンバーではなく、クラス自体に関連するメンバーです。静的メンバーを使用することで、クラスのインスタンスを作成することなく、クラス自体からメソッドやプロパティにアクセスすることができます。

以下は、静的メンバーを使用した例です。
class MathUtils {
  static PI: number = 3.14;
  static circumference(radius: number): number {
    return 2 * this.PI * radius;
  }
}

この例では、MathUtilsクラスを定義しています。MathUtilsクラスには、PIという静的プロパティと、circumferenceという静的メソッドがあります。PIプロパティは、staticキーワードを使用して静的プロパティに指定されています。また、circumferenceメソッド内で使用されるPIプロパティには、thisキーワードを使用してアクセスしています。

静的メンバーは、クラス自体に関連するメソッドやプロパティを定義するために使用されます。例えば、上記のMathUtilsクラスでは、円周を計算するためのcircumferenceメソッドが定義されています。このようなメソッドは、クラス自体に関連する機能であるため、静的メソッドとして定義することが適切です。

抽象クラス

TypeScriptには、抽象クラスという機能があります。抽象クラスは、インスタンス化できないクラスであり、抽象メソッドという未実装のメソッドを含むことができます。抽象クラスを使用することで、派生クラスで共通の振る舞いを定義することができます。

以下は、抽象クラスを使用した例です。

abstract class Shape {
  protected color: string;
  constructor(color: string) {
    this.color = color;
  }
  abstract getArea(): number;
  abstract getPerimeter(): number;
  getColor(): string {
    return this.color;
  }
}

class Circle extends Shape {
  private radius: number;
  constructor(radius: number, color: string) {
    super(color);
    this.radius = radius;
  }
  getArea(): number {
    return Math.PI * this.radius ** 2;
  }
  getPerimeter(): number {
    return 2 * Math.PI * this.radius;
  }
}

class Rectangle extends Shape {
  private width: number;
  private height: number;
  constructor(width: number, height: number, color: string) {
    super(color);
    this.width = width;
    this.height = height;
  }
  getArea(): number {
    return this.width * this.height;
  }
  getPerimeter(): number {
    return 2 * (this.width + this.height);
  }
}

この例では、Shapeという抽象クラスを定義しています。Shapeクラスには、colorプロパティと、getAreaとgetPerimeterという未実装の抽象メソッドがあります。また、getColorメソッドが定義されています。

CircleクラスとRectangleクラスは、Shapeクラスを継承しています。Circleクラスでは、radiusプロパティとgetAreaメソッドとgetPerimeterメソッドが定義されています。Rectangleクラスでも同様に、widthプロパティとheightプロパティとgetAreaメソッドとgetPerimeterメソッドが定義されています。

抽象クラスの利点は、継承するクラスが共通の振る舞いを持ち、さらにその振る舞いをカスタマイズできることです。例えば、上記の例では、ShapeクラスにgetAreaとgetPerimeterという抽象メソッドがありますが、これらのメソッドはCircleクラスとRectangleクラスで実装されています。これにより、それぞれのクラスで異なる振る舞いを持つことができます。

クラスの継承

TypeScriptでは、クラスの継承がサポートされています。継承は、既存のクラスを基にして、新しいクラスを定義するための機能です。継承によって、既存のクラスの機能を再利用することができます。

以下は、クラスの継承を使用した例です。
class Animal {
  protected name: string;
  constructor(name: string) {
    this.name = name;
  }
  move(distance: number = 0) {
    console.log(`${this.name} moved ${distance}m.`);
  }
}

class Dog extends Animal {
  constructor(name: string) {
    super(name);
  }
  bark() {
    console.log(`${this.name} barked!`);
  }
}

class Snake extends Animal {
  constructor(name: string) {
    super(name);
  }
  move(distance: number = 5) {
    console.log(`${this.name} slithered ${distance}m.`);
  }
}

class Horse extends Animal {
  constructor(name: string) {
    super(name);
  }
  move(distance: number = 45) {
    console.log(`${this.name} galloped ${distance}m.`);
  }
}

let dog = new Dog("Fido");
dog.move(10); // Fido moved 10m.
dog.bark(); // Fido barked!

let snake = new Snake("Sammy");
snake.move(); // Sammy slithered 5m.

let horse = new Horse("Tom");
horse.move(); // Tom galloped 45m.

この例では、Animalクラスを定義しています。Animalクラスには、nameプロパティとmoveメソッドがあります。Dogクラス、Snakeクラス、Horseクラスは、Animalクラスを継承しています。それぞれのクラスでは、Animalクラスから継承したnameプロパティとmoveメソッドを使用しています。

また、Dogクラスには、barkメソッドが定義されています。Snakeクラスには、moveメソッドをオーバーライドして、動き方を変更しています。

この例では、Dogクラス、Snakeクラス、HorseクラスがAnimalクラスから継承されたプロパティとメソッドを使用しています。これにより、クラスの再利用が可能となります。また、各クラスは自身の特有の振る舞いを持っています。

インターフェース

TypeScriptでは、インターフェースを使用して、オブジェクトの形状を定義することができます。インターフェースは、オブジェクトが持つべきプロパティやメソッドを定義するためのものです。

以下は、インターフェースの例です。

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

function greet(person: Person) {
  console.log(`Hello, ${person.name}! You are ${person.age} years old.`);
}

let john = { name: "John", age: 30 };
greet(john); // Hello, John! You are 30 years old.

この例では、Personというインターフェースを定義しています。Personインターフェースには、nameプロパティとageプロパティがあります。greet関数は、Personインターフェースを引数に受け取り、nameプロパティとageプロパティを使用して挨拶をします。

johnオブジェクトは、Personインターフェースを満たしているため、greet関数の引数に渡すことができます。

ジェネリック

TypeScriptでは、ジェネリックを使用して、汎用的なコードを書くことができます。ジェネリックは、型パラメータを使用して、コードの再利用性を高めることができます。

以下は、ジェネリックの例です。

function identity<T>(arg: T): T {
  return arg;
}

let output1 = identity<string>("hello");
let output2 = identity<number>(42);

console.log(output1); // hello
console.log(output2); // 42

この例では、identityという関数を定義しています。identity関数は、ジェネリックを使用しているため、任意の型の引数を受け取ることができます。identity関数は、引数と同じ型の値を返します。

output1変数には、identity<string>を呼び出した結果が代入されています。この場合、helloが返されるため、output1変数の値はhelloになります。同様に、output2変数には、identity<number>を呼び出た結果が代入されています。この場合、42が返されるため、output2変数の値は42になります。

モジュール

TypeScriptでは、モジュールを使用して、コードを論理的にグループ化することができます。モジュールは、ファイル、名前空間、または外部モジュールの形式で定義することができます。

以下は、外部モジュールの例です。

// math.ts
export function sum(x: number, y: number): number {
  return x + y;
}

// main.ts
import { sum } from "./math";

let result = sum(1, 2);
console.log(result); // 3

この例では、mathという外部モジュールを定義しています。mathモジュールには、sum関数が定義されています。

mainというファイルで、mathモジュールからsum関数をインポートしています。sum関数は、1と2を引数に渡された結果が、result変数に代入され、3が出力されます。

結論

これらは、TypeScriptの基本的な機能の一部です。TypeScriptには、多くの機能があり、さまざまなシナリオに適した方法で使用することができます。この記事を通じて、TypeScriptの基本的な概念について理解することができました。これをもとに、より高度なTypeScriptのコードを書くための準備が整いました。

Discussion