第6章クラスと継承

【TypeScript入門シリーズ】第6章:クラスと継承

TypeScriptのクラスと継承は、オブジェクト指向プログラミングの概念をサポートしており、再利用性や拡張性の高いコードを書くために非常に重要です。クラスを使うことで、オブジェクトの設計をシンプルにし、共通の振る舞いをもつオブジェクトを効率的に作成することができます。

この章では、TypeScriptにおけるクラスの定義から、クラス間での継承アクセス修飾子の使い方まで、オブジェクト指向プログラミングの基礎を学びます。


5.1 クラスの基本

TypeScriptのクラスは、オブジェクトの設計図です。クラスには、プロパティメソッドを定義して、オブジェクトの状態と動作を記述することができます。TypeScriptでは、JavaScriptのES6(ECMAScript 2015)で導入されたクラスを基盤にしつつ、型注釈を使って安全性を高めています。

5.1.1 クラスの定義

TypeScriptでクラスを定義するには、classキーワードを使います。クラス内には、プロパティやメソッドを定義してオブジェクトの構造を設計します。

class Person {
name: string;
age: number;

// コンストラクタ: クラスのインスタンスが作られるときに呼ばれる
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}

// メソッド: クラスに属する関数
greet(): string {
return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
}
}

// クラスのインスタンスを作成
let john = new Person("John", 30);
console.log(john.greet()); // "Hello, my name is John and I am 30 years old."

この例では、Personというクラスを定義し、nameageというプロパティ、そしてgreet()というメソッドを持っています。クラスのインスタンスを作成するためには、newキーワードを使ってインスタンス化します。

5.1.2 コンストラクタ

コンストラクタは、クラスからオブジェクトが作られるときに自動的に呼び出される特別なメソッドです。TypeScriptでは、コンストラクタ内でオブジェクトのプロパティを初期化することが一般的です。

コンストラクタは、次のようにconstructorという名前で定義します。

class Car {
brand: string;
model: string;

constructor(brand: string, model: string) {
this.brand = brand;
this.model = model;
}

displayInfo(): string {
return `This car is a ${this.brand} ${this.model}.`;
}
}

let myCar = new Car("Toyota", "Corolla");
console.log(myCar.displayInfo()); // "This car is a Toyota Corolla."

この例では、Carクラスにbrandmodelというプロパティが定義され、コンストラクタでその値が設定されます。インスタンスが作成されるたびに、コンストラクタが実行され、オブジェクトの初期化が行われます。


5.2 クラスのメソッド

クラス内で定義されたメソッドは、インスタンス化されたオブジェクトに対して呼び出すことができます。メソッドはクラスの動作を定義し、クラスのプロパティにアクセスしてその値を操作したり表示したりすることができます。

5.2.1 メソッドの定義

クラスメソッドは、クラス内で通常の関数のように定義します。メソッド内でクラスのプロパティにアクセスするには、thisキーワードを使います。

class Rectangle {
width: number;
height: number;

constructor(width: number, height: number) {
this.width = width;
this.height = height;
}

// 面積を計算するメソッド
calculateArea(): number {
return this.width * this.height;
}
}

let rect = new Rectangle(10, 5);
console.log(rect.calculateArea()); // 50

この例では、RectangleクラスにcalculateArea()というメソッドが定義されています。このメソッドは、this.widththis.heightにアクセスし、長方形の面積を計算します。


5.3 アクセス修飾子

アクセス修飾子は、クラスのプロパティやメソッドへのアクセスを制御するために使います。TypeScriptでは、次の3つのアクセス修飾子が用意されています。

  • public: デフォルトのアクセス修飾子で、クラスの外部からでもアクセス可能。
  • private: クラス内からのみアクセス可能で、クラス外からはアクセスできない。
  • protected: クラス内および継承したサブクラスからアクセス可能。

5.3.1 public修飾子

publicは、デフォルトのアクセス修飾子です。publicで宣言されたプロパティやメソッドは、クラス外からも自由にアクセスできます。

class Animal {
public name: string;

constructor(name: string) {
this.name = name;
}

public speak(): void {
console.log(`${this.name} makes a noise.`);
}
}

let cat = new Animal("Cat");
cat.speak(); // "Cat makes a noise."

この例では、nameプロパティとspeak()メソッドがpublicとして宣言されており、クラスの外部からもアクセスできます。

5.3.2 private修飾子

privateで宣言されたプロパティやメソッドは、クラス内からのみアクセス可能です。外部からの直接アクセスを防ぐことで、クラスの内部構造を隠蔽できます。

class BankAccount {
private balance: number;

constructor(initialBalance: number) {
this.balance = initialBalance;
}

public deposit(amount: number): void {
this.balance += amount;
}

public getBalance(): number {
return this.balance;
}
}

let account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
// account.balance = 0; // エラー: balanceはprivateでアクセスできない

この例では、balanceプロパティがprivateとして宣言されています。そのため、クラス外から直接balanceにアクセスすることはできませんが、deposit()getBalance()を通じて間接的に操作できます。

5.3.3 protected修飾子

protectedは、クラス内およびそのクラスを継承したサブクラスからアクセス可能な修飾子です。外部からはアクセスできませんが、サブクラスで利用したいプロパティやメソッドに使用します。

class Vehicle {
protected speed: number;

constructor(speed: number) {
this.speed = speed;
}

protected accelerate(amount: number): void {
this.speed += amount;
}
}

class Car extends Vehicle {
private brand: string;

constructor(brand: string, speed: number) {
super(speed);
this.brand = brand;
}

public speedUp(): void {
this.accelerate(10);
console.log(`${this.brand} is now going at ${this.speed} km/h.`);
}
}

let myCar = new Car("Toyota", 60);
myCar.speedUp(); // "Toyota is now going at 70 km/h."
// myCar.speed = 100; // エラー: speedはprotectedでアクセスできない

この例では、Vehicleクラスのspeedプロパティとaccelerate()メソッドがprotectedとして宣言されています。そのため、サブクラスであるCarクラス内からはアクセス可能ですが、外部からはアクセスできません。


5.4 クラスの継承

継承は、オブジェクト指向プログラミングの重要な概念の一つで、既存のクラスを基にして新しいクラスを作成することができます。TypeScriptでは、extendsキーワードを使ってクラスを継承します。

5.4.1 クラスの基本的な継承

クラスを継承することで、既存のクラス(親クラス)のプロパティやメソッドを再利用し、サブクラスでさらに新しい機能を追加できます。

class Animal {
name: string;

constructor(name: string) {
this.name = name;
}

makeSound(): void {
console.log(`${this.name} makes a sound.`);
}
}

class Dog extends Animal {
constructor(name: string) {
super(name); // 親クラスのコンストラクタを呼び出す
}

// 親クラスのメソッドを上書き(オーバーライド)
makeSound(): void {
console.log(`${this.name} barks.`);
}
}

let dog = new Dog("Rex");
dog.makeSound(); // "Rex barks."

この例では、DogクラスがAnimalクラスを継承しています。super()を使って親クラスのコンストラクタを呼び出し、親クラスのプロパティやメソッドを利用しつつ、新しいメソッドを追加しています。

5.4.2 メソッドのオーバーライド

サブクラスは、親クラスで定義されたメソッドをオーバーライド(上書き)することができます。サブクラス内で同じ名前のメソッドを定義すると、そのメソッドは親クラスのメソッドを上書きし、サブクラス独自の動作を持つようになります。

class Bird extends Animal {
constructor(name: string) {
super(name);
}

// 親クラスのmakeSoundメソッドをオーバーライド
makeSound(): void {
console.log(`${this.name} chirps.`);
}
}

let bird = new Bird("Sparrow");
bird.makeSound(); // "Sparrow chirps."

この例では、Birdクラスが親クラスAnimalmakeSound()メソッドをオーバーライドして、鳥の鳴き声を定義しています。


5.5 クラスとインターフェースの関係

クラスとインターフェースは、TypeScriptでよく一緒に使われます。インターフェースを使うことで、クラスがどのようなプロパティやメソッドを実装すべきかを指定できます。これにより、クラスの構造が明確になり、コードの再利用性や保守性が向上します。

5.5.1 クラスにインターフェースを実装する

クラスがインターフェースを**実装(implement)**する場合、implementsキーワードを使います。インターフェースで定義されたプロパティやメソッドを、クラス内で必ず実装しなければなりません。

interface Movable {
move(): void;
}

class Car implements Movable {
move(): void {
console.log("The car is moving.");
}
}

let myCar = new Car();
myCar.move(); // "The car is moving."

この例では、Movableインターフェースがmove()というメソッドを定義しており、Carクラスがそれを実装しています。


まとめ

この章では、TypeScriptにおけるクラス継承について詳しく学びました。クラスを使うことでオブジェクト指向プログラミングの概念を活用でき、再利用性や保守性が向上します。また、アクセス修飾子を使ったデータの隠蔽や、継承によるクラスの拡張も学びました。これにより、より複雑で高度なアプリケーションを構築するための基盤が整いました。