404 Not Found

404 Not Found


nginx

TypeScript のクラスとインターフェース

クラスとインターフェースは、TypeScriptにおけるオブジェクト指向プログラミングの2つの柱です。クラスは実装を提供し、インターフェースは契約を定義します。クラスがインターフェースを実装する場合、TypeScriptはそのクラスがインターフェースで指定されたすべての要件を満たしていることを保証します。

1. クラスがインターフェースを実装する(implements)

(1) 基本的な構文

TYPESCRIPT
interface Printable {
  toString(): string;
}

class User implements Printable {
  constructor(public name: string, public age: number) {}

  toString(): string {
    return `${this.name},${this.age} years old`;
  }
}

let user = new User("Charlie", 20);
console.log(user.toString());  // "Charlie,20 years old"

(2) インターフェースの実装の意義

インターフェースとは「契約」のことです。クラスがインターフェースを実装する場合、そのクラスは「このインターフェースで要求されるすべてのプロパティとメソッドを提供する」と約束することになります:

TYPESCRIPT
interface Animal {
  name: string;
  speak(): string;
  move(distance: number): void;
}

class Dog implements Animal {
  constructor(public name: string) {}

  speak(): string {
    return "Woof!";
  }

  move(distance: number): void {
    console.log(`${this.name} Moved ${distance}m`);
  }
}

// ❌ Members that do not meet the interface requirements will result in an error.
// class Cat implements Animal {
//   constructor(public name: string) {}
//   // Missing speak and move → Compilation Error
// }

(3) マルチインターフェースの実装

1つのクラスは、カンマで区切って複数のインターフェースを実装することができます:

TYPESCRIPT
interface Serializable {
  serialize(): string;
}

interface Comparable {
  compareTo(other: this): number;
}

class Score implements Serializable, Comparable {
  constructor(public value: number) {}

  serialize(): string {
    return JSON.stringify({ value: this.value });
  }

  compareTo(other: Score): number {
    return this.value - other.value;
  }
}

let s1 = new Score(90);
let s2 = new Score(85);

console.log(s1.serialize());       // '{"value":90}'
console.log(s1.compareTo(s2));     // 5(Positive Number Display s1 > s2)

▶ 例:インターフェースを用いた統一APIの定義

TYPESCRIPT
interface Repository<T> {
  findById(id: number): T | null;
  findAll(): T[];
  save(entity: T): void;
  delete(id: number): boolean;
}

interface User {
  id: number;
  name: string;
  email: string;
}

class UserRepository implements Repository<User> {
  private users: User[] = [];

  findById(id: number): User | null {
    return this.users.find(u => u.id === id) ?? null;
  }

  findAll(): User[] {
    return [...this.users];
  }

  save(entity: User): void {
    let index = this.users.findIndex(u => u.id === entity.id);
    if (index >= 0) {
      this.users[index] = entity;  // Update
    } else {
      this.users.push(entity);     // New
    }
  }

  delete(id: number): boolean {
    let len = this.users.length;
    this.users = this.users.filter(u => u.id !== id);
    return this.users.length < len;
  }
}

let repo = new UserRepository();
repo.save({ id: 1, name: "Charlie", email: "xiao@example.com" });
repo.save({ id: 2, name: "Diana", email: "hong@example.com" });

console.log(repo.findById(1)?.name);  // "Charlie"
console.log(repo.findAll().length);   // 2

出力:

TEXT
Charlie
2

2. インターフェースを継承するクラス

TypeScriptのユニークな特徴として、インターフェースはクラスからメンバー定義を継承することができます(型宣言のみを継承し、実装は継承しません):

TYPESCRIPT
class Control {
  private state: string = "active";

  protected getState(): string {
    return this.state;
  }
}

// Interface-Inherited Classes——Inherit only the type signature
interface Selectable extends Control {
  select(): void;
}

// When a class implements an interface, it must provide all members.
class Dropdown implements Selectable {
  private state: string = "active";  // You must implement it yourself. private Members

  protected getState(): string {
    return this.state;
  }

  select(): void {
    console.log("Selected:" + this.getState());
  }
}
💡 目的: インターフェースを継承するクラスは、主に複雑なクラス階層において、「特定のクラスの構造を持ちつつ、追加の機能も備えている」型を記述するために使用されます。日常的な開発ではあまり使用されません。


3. 抽象クラス (abstract)

抽象クラスとは「未完成のクラス」であり、部分的な実装しか提供せず、サブクラスが残りの部分を完成させなければなりません:

(1) 基本的な構文

TYPESCRIPT
abstract class Shape {
  // Abstract Methods——Not implemented,Subclasses must implement
  abstract getArea(): number;
  abstract getPerimeter(): number;

  // Specific Methods——Implemented,Direct Inheritance by Subclasses
  describe(): string {
    return `Area:${this.getArea().toFixed(2)},Perimeter:${this.getPerimeter().toFixed(2)}`;
  }
}

class Circle extends Shape {
  constructor(public radius: number) {
    super();
  }

  getArea(): number {
    return Math.PI * this.radius ** 2;
  }

  getPerimeter(): number {
    return 2 * Math.PI * this.radius;
  }
}

class Rectangle extends Shape {
  constructor(public width: number, public height: number) {
    super();
  }

  getArea(): number {
    return this.width * this.height;
  }

  getPerimeter(): number {
    return 2 * (this.width + this.height);
  }
}

// ❌ Abstract classes cannot be instantiated directly.
// let shape = new Shape();

let circle = new Circle(5);
let rect = new Rectangle(4, 6);

console.log(circle.describe());  // "Area:78.54,Perimeter:31.42"
console.log(rect.describe());    // "Area:24.00,Perimeter:20.00"

(2) 抽象クラスとインターフェース

機能 抽象クラス インターフェース
実装方法 特定の実装が存在する場合がある いかなる実装も存在してはならない
コンストラクタ 持つことができる 持つことができない
多重継承 単一継承のみ 多重継承が可能(extends)
アクセス修飾子 public/private/protected をサポート プロパティはデフォルトで public です
インスタンス化 直接インスタンス化できない インスタンス化できない(純粋型)
実行時に存在するか はい(JSクラスにコンパイルされる) いいえ(純粋な型宣言)

(3) 抽象クラスを使用するタイミング

(4) インターフェースを使用するタイミング

▶ 例:抽象クラスを使ったテンプレートメソッドパターンの実装

TYPESCRIPT
abstract class DataParser {
  // Template Method——Define the Algorithm Framework
  parse(input: string): string {
    let raw = this.read(input);
    let validated = this.validate(raw);
    let transformed = this.transform(validated);
    return this.format(transformed);
  }

  // Specific Methods——Shared by all subclasses
  private read(input: string): string {
    return input.trim();
  }

  // Abstract Methods——Each subclass implements it on its own
  protected abstract validate(data: string): string;
  protected abstract transform(data: string): string;

  // Hook Method——Subclasses may choose to override this method
  protected format(data: string): string {
    return data;
  }
}

class UpperParser extends DataParser {
  protected validate(data: string): string {
    if (data.length === 0) throw new Error("Input is empty");
    return data;
  }

  protected transform(data: string): string {
    return data.toUpperCase();
  }
}

class ReverseParser extends DataParser {
  protected validate(data: string): string {
    return data;
  }

  protected transform(data: string): string {
    return data.split("").reverse().join("");
  }

  protected format(data: string): string {
    return `[Reversal] ${data}`;
  }
}

let upper = new UpperParser();
let reverse = new ReverseParser();

console.log(upper.parse("  hello world  "));     // "HELLO WORLD"
console.log(reverse.parse("  hello world  "));    // "[Reversal] dlrow olleh"

出力:

TEXT
HELLO WORLD
[Reversal] dlrow olleh

4. クラスを型として使用する方法

クラス定義はそれ自体が型であり、変数の型としてクラス名を使用することができます:

(1) クラスの概念

TYPESCRIPT
class Point {
  constructor(public x: number, public y: number) {}

  distanceTo(other: Point): number {
    return Math.sqrt((this.x - other.x)  2 + (this.y - other.y)  2);
  }
}

// Class as a Type——Accept Point Instances or structurally compatible objects
let p1: Point = new Point(1, 2);
let p2: Point = { x: 3, y: 4 };   // ✅ Structural Compatibility(But does not include methods!)

console.log(p1.distanceTo(p2));    // ✅ p1 has distanceTo method
// p2.distanceTo(p1);              // ❌ p2 Only x and y,There is no way
⚠️ 注意: クラスの「型」セクションには、インスタンスのプロパティとメソッドのシグネチャのみが含まれます。構造体互換のオブジェクトを代入する場合、メソッドは含まれず、プロパティのみが照合されます。メソッドにアクセスするには、new Point() を使用してインスタンスを作成する必要があります。

(2) クラスタイプと typeof 演算子

TYPESCRIPT
class Factory {
  static create(): Factory {
    return new Factory();
  }

  constructor(public name: string) {}
}

// Factory Type——Instance Type
let instance: Factory = new Factory("Products");

// typeof Factory Type——The class itself(Constructor Types)
let FactoryClass: typeof Factory = Factory;
let another = FactoryClass.create();  // ✅ Calling a Static Method

❓ よくある質問

Q implementsextends の違いは何ですか?
A implements は「インターフェースを実装する」という意味です。つまり、クラスはインターフェースで要求されるすべてのメンバを提供することを約束しますが、実装コードは一切継承しません。extendsは「クラスを継承する」ことを意味します。つまり、サブクラスはスーパークラスからすべての実装コードを継承し、それをオーバーライドすることができます。クラスは複数のインターフェースをimplementsできますが、スーパークラスは1つしかextendsできません。
Q 抽象クラスとインターフェース、どちらを使うべきですか?
A 簡単なルールとして、複数のクラスで実装コードを共有する必要がある場合は抽象クラスを使い、「何ができるか」を規定する契約を定義するだけでよい場合はインターフェースを使います。この2つを組み合わせることも可能です。つまり、インターフェースで機能定義を行い、抽象クラスで部分的な実装を提供するという形です。
Q インターフェースを実装するクラスは、追加のメンバを持つことができますか?
A はい。インターフェースを実装するクラスは、「インターフェースで指定されたメンバーを少なくとも持つ」ことだけが求められており、追加のメンバーを持つことは全く問題ありません。例えば、interface Printable { print(): void }class Document implements Printable { print() {...}; save() {...} }Documentクラスには追加のsaveメソッドがありますが、これは全く問題ありません。
Q インターフェースは抽象クラスから継承できますか?
A はい。interface ISelect extends AbstractControl {} これは有効です。インターフェースは、抽象クラスのすべてのメンバのシグネチャ(プライベートメンバのシグネチャも含む)を継承します。ただし、このインターフェースを実装するクラスは、プライベートメンバを含め、すべてのメンバに対して独自の実装を提供する必要があります。

📖 まとめ

📝 練習問題

  1. 基本問題(難易度 ⭐)Loggable インターフェース(log メソッドを含む)を定義し、User クラスと Product クラスにそれぞれこのインターフェースを実装させます。インスタンスを作成し、log メソッドを呼び出してください。
  2. 上級問題(難易度 ⭐⭐): 抽象クラス Vehiclebrand プロパティ、抽象メソッド start、および具象メソッド describe を持つ)を定義し、それぞれ start メソッドを実装するサブクラス Car および Bike を作成してください。型 Vehicle のパラメータを受け取り、describe メソッドを呼び出す関数を記述してください。
  3. 課題(難易度:⭐⭐⭐)SortableCollection<T> インターフェース(length プロパティ、compare メソッド、および swap メソッドを持つ)を設計し、NumberCollection クラスにそれを実装させます。次に、Sorter 抽象クラス内で(インターフェースのメソッドを使用して)バブルソートアルゴリズムを実装し、サブクラスが compare および swap の具体的な実装のみを提供すればよいようにします。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%