チュートリアル

TypeScript基礎:クラス

TypeScript入門クラスオブジェクト指向
広告エリア

クラスの定義

class User {
  // プロパティの宣言
  name: string;
  age: number;

  // コンストラクタ
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // メソッド
  greet(): string {
    return `こんにちは、${this.name}です`;
  }
}

const user = new User("太郎", 25);
console.log(user.greet());  // "こんにちは、太郎です"

アクセス修飾子

public, private, protected

class Person {
  public name: string;        // どこからでもアクセス可能(デフォルト)
  private age: number;        // クラス内からのみ
  protected id: number;       // クラスとサブクラスから
  readonly country: string;   // 読み取り専用

  constructor(name: string, age: number, id: number) {
    this.name = name;
    this.age = age;
    this.id = id;
    this.country = "Japan";
  }

  getAge(): number {
    return this.age;  // クラス内からはアクセス可能
  }
}

const person = new Person("太郎", 25, 1);
console.log(person.name);     // OK
// console.log(person.age);   // エラー:private
// console.log(person.id);    // エラー:protected
// person.country = "USA";    // エラー:readonly

パラメータプロパティ

コンストラクタ引数にアクセス修飾子を付けると、自動的にプロパティになります。

class User {
  // 簡潔な書き方
  constructor(
    public name: string,
    private age: number,
    readonly id: number
  ) {}

  getAge(): number {
    return this.age;
  }
}

// 上記は以下と同等
class UserVerbose {
  public name: string;
  private age: number;
  readonly id: number;

  constructor(name: string, age: number, id: number) {
    this.name = name;
    this.age = age;
    this.id = id;
  }
}

プロパティ

オプショナルプロパティ

class User {
  name: string;
  email?: string;  // オプショナル

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

const user1 = new User("太郎");
const user2 = new User("花子", "hanako@example.com");

プロパティ初期化子

class Config {
  debug: boolean = false;
  maxItems: number = 100;
  theme: string = "light";

  constructor(options?: Partial<Config>) {
    Object.assign(this, options);
  }
}

const config = new Config({ debug: true });
console.log(config.debug);     // true
console.log(config.maxItems);  // 100

ゲッターとセッター

class Circle {
  private _radius: number;

  constructor(radius: number) {
    this._radius = radius;
  }

  // ゲッター
  get radius(): number {
    return this._radius;
  }

  // セッター
  set radius(value: number) {
    if (value < 0) {
      throw new Error("半径は0以上である必要があります");
    }
    this._radius = value;
  }

  // 計算プロパティ(ゲッターのみ)
  get area(): number {
    return Math.PI * this._radius ** 2;
  }
}

const circle = new Circle(5);
console.log(circle.radius);  // 5
console.log(circle.area);    // 78.54...
circle.radius = 10;
// circle.radius = -1;       // エラー

静的メンバー

class MathUtils {
  static readonly PI = 3.14159;
  private static instanceCount = 0;

  constructor() {
    MathUtils.instanceCount++;
  }

  static add(a: number, b: number): number {
    return a + b;
  }

  static getInstanceCount(): number {
    return MathUtils.instanceCount;
  }
}

console.log(MathUtils.PI);        // 3.14159
console.log(MathUtils.add(1, 2)); // 3

new MathUtils();
new MathUtils();
console.log(MathUtils.getInstanceCount());  // 2

静的ブロック

class Database {
  static connection: string;

  static {
    // 静的初期化ブロック
    console.log("Databaseクラスの初期化");
    Database.connection = "localhost:5432";
  }
}

抽象クラス

インスタンス化できない基底クラスです。

abstract class Shape {
  constructor(public name: string) {}

  // 抽象メソッド(サブクラスで実装必須)
  abstract area(): number;
  abstract perimeter(): number;

  // 通常のメソッド
  describe(): string {
    return `${this.name}: 面積=${this.area()}, 周囲=${this.perimeter()}`;
  }
}

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

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

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

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

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

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

// const shape = new Shape("形");  // エラー:抽象クラスはインスタンス化不可
const rect = new Rectangle(10, 5);
console.log(rect.describe());

クラス式

// 無名クラス式
const User = class {
  constructor(public name: string) {}
};

// 名前付きクラス式
const Product = class ProductClass {
  constructor(public name: string, public price: number) {}
};

const user = new User("太郎");
const product = new Product("本", 1500);

thisの型

class Builder {
  private value: string = "";

  append(text: string): this {
    this.value += text;
    return this;  // メソッドチェーン用
  }

  build(): string {
    return this.value;
  }
}

const result = new Builder()
  .append("Hello, ")
  .append("World!")
  .build();

console.log(result);  // "Hello, World!"

実践例:イベントエミッター

type EventHandler<T = void> = (data: T) => void;

class EventEmitter<Events extends Record<string, unknown>> {
  private handlers = new Map<keyof Events, Set<EventHandler<unknown>>>();

  on<K extends keyof Events>(
    event: K,
    handler: EventHandler<Events[K]>
  ): void {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, new Set());
    }
    this.handlers.get(event)!.add(handler as EventHandler<unknown>);
  }

  off<K extends keyof Events>(
    event: K,
    handler: EventHandler<Events[K]>
  ): void {
    this.handlers.get(event)?.delete(handler as EventHandler<unknown>);
  }

  emit<K extends keyof Events>(event: K, data: Events[K]): void {
    this.handlers.get(event)?.forEach(handler => handler(data));
  }
}

// 使用例
interface AppEvents {
  login: { userId: number; timestamp: Date };
  logout: void;
  error: Error;
}

const emitter = new EventEmitter<AppEvents>();

emitter.on("login", (data) => {
  console.log(`User ${data.userId} logged in`);
});

emitter.on("error", (error) => {
  console.error(error.message);
});

emitter.emit("login", { userId: 1, timestamp: new Date() });

実践例:シングルトン

class Database {
  private static instance: Database;
  private connected: boolean = false;

  private constructor() {
    // privateコンストラクタ
  }

  static getInstance(): Database {
    if (!Database.instance) {
      Database.instance = new Database();
    }
    return Database.instance;
  }

  connect(): void {
    this.connected = true;
    console.log("データベースに接続しました");
  }

  isConnected(): boolean {
    return this.connected;
  }
}

const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2);  // true(同じインスタンス)

まとめ

  • public, private, protectedでアクセス制御
  • パラメータプロパティで簡潔に記述
  • readonlyで読み取り専用プロパティ
  • get/setでゲッター・セッター
  • staticでクラスレベルのメンバー
  • abstractで抽象クラス・抽象メソッド
  • thisの型でメソッドチェーン

次回は継承とインターフェースについて学びます。

広告エリア