TypeScript

クラスと設計

関連する状態と操作をまとめ、外部へ公開する約束を型で表しましょう。

01

クラスからオブジェクトを作る

クラスはデータと、それを操作するメソッドをまとめる方法です。newで個別のインスタンスを作ります。

商品クラスTYPESCRIPT
class Product {
  constructor(
    public readonly name: string,
    public price: number,
  ) {}

  priceWithTax(): number {
    return this.price + Math.floor(this.price * 0.1);
  }
}

const notebook = new Product("ノート", 200);
console.log(notebook.name);
console.log(notebook.priceWithTax());
実行結果OUTPUT
ノート
220

コンストラクター引数へアクセス修飾子を書くと、同名のプロパティ宣言と代入をまとめられます。

02

不正な変更を防ぐ

privateはクラス外から直接触れないプロパティです。値を変更する手順をメソッドへ集め、条件を守ります。

残高を管理TYPESCRIPT
class Wallet {
  private balance = 0;

  deposit(amount: number): void {
    if (amount <= 0) throw new Error("金額は正数にしてください");
    this.balance += amount;
  }

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

const wallet = new Wallet();
wallet.deposit(1000);
console.log(wallet.getBalance());
実行結果OUTPUT
1000
privateは入力検証の代わりではない 外部APIやデータベースから来る値は、クラスへ渡す前または生成時にも検証します。
03

interfaceで公開する形を表す

interfaceはオブジェクトが満たすべき形を宣言できます。データの型にはtypeも利用でき、ユニオン型などはtypeが得意です。

保存処理の約束TYPESCRIPT
type User = { id: number; name: string };

interface UserRepository {
  findById(id: number): User | undefined;
  save(user: User): void;
}

利用側は保存先が配列かデータベースかではなく、このメソッドの形だけに依存できます。

04

implementsで約束を満たす

メモリへ保存TYPESCRIPT
type User = { id: number; name: string };

interface UserRepository {
  findById(id: number): User | undefined;
  save(user: User): void;
}

class MemoryUserRepository implements UserRepository {
  private users: User[] = [];

  findById(id: number): User | undefined {
    return this.users.find((user) => user.id === id);
  }

  save(user: User): void {
    this.users.push(user);
  }
}

const repository = new MemoryUserRepository();
repository.save({ id: 1, name: "Mity" });
console.log(repository.findById(1)?.name);
実行結果OUTPUT
Mity

findById(1)undefinedを返す可能性があります。?.は値がundefinedなら.nameへ進まずundefinedを返すため、プロパティアクセスの実行時エラーを防げます。詳しくはnullとundefinedを安全に扱うで確認できます。

implementsはメソッドの存在と型を検査しますが、処理内容が正しいことまでは保証しません。振る舞いはテストで確認します。

05

継承だけでなく委譲も考える

既存クラスの機能を使いたいだけなら、extendsで親子関係を作るより、必要なオブジェクトを受け取って処理を任せる委譲が変更しやすい場合があります。

通知方法を差し替えるTYPESCRIPT
interface Notifier {
  send(message: string): void;
}

class ConsoleNotifier implements Notifier {
  send(message: string): void {
    console.log(`[通知] ${message}`);
  }
}

class OrderService {
  constructor(private readonly notifier: Notifier) {}

  complete(): void {
    this.notifier.send("注文が完了しました");
  }
}

new OrderService(new ConsoleNotifier()).complete();
実行結果OUTPUT
[通知] 注文が完了しました

OrderServiceは通知の具体的な方法を知らず、Notifierの約束だけを利用します。

PRACTICE

ミニ課題:在庫を管理する

商品名と在庫数を持つInventoryItemクラスを作ってください。在庫数は外から直接変更できないようにし、正数を追加するrestockと、在庫が足りる場合だけ減らすshipを用意します。

確認する結果 在庫5から2個発送すると3を表示し、4個発送しようとした場合はエラーになることを確認します。