TypeScript | 関数・クラス・ジェネリクス:クラス設計 – private constructorの用途

TypeScript TypeScript
スポンサーリンク

ゴール:「new させないクラス」に意味を持たせる感覚をつかむ

private constructor は一言でいうと、

「このクラスは、外から new してほしくない」と宣言するための仕組み

です。

「え、クラスって new して使うものでしょ?」と思うかもしれませんが、
あえて「new させない」ことで、設計がきれいになる場面がいくつかあります。

ここでは代表的な用途を、初心者向けにかみ砕いて説明していきます。

基本:private constructor とは何か

通常のコンストラクタとの違い

普通のクラスはこう書きます。

class User {
  constructor(public name: string) {}
}

const u = new User("Taro"); // どこからでも new できる
TypeScript

コンストラクタが public(省略時のデフォルト)なので、
どのファイルからでも new User(...) ができます。

private constructor にするとこうなります。

class User {
  private constructor(public name: string) {}
}

// const u = new User("Taro"); // エラー:コンストラクタが private なので外から new できない
TypeScript

この時点で、

「このクラスは、クラスの外側からは new できない」

という制約がかかります。

では、そんなクラスをどうやって使うのか?
ここからが本題です。

用途1:シングルトン(インスタンスを1つだけにしたい)

「このクラスのインスタンスは1個だけでいい」パターン

シングルトンは、

「アプリ全体で、このクラスのインスタンスは1つだけにしたい」

というときの定番パターンです。

private constructor は、これを TypeScript で表現するときにとても相性がいいです。

class AppConfig {
  private static instance: AppConfig | null = null;

  private constructor(
    public readonly apiEndpoint: string,
    public readonly timeoutMs: number
  ) {}

  static getInstance(): AppConfig {
    if (this.instance === null) {
      this.instance = new AppConfig("https://api.example.com", 5000);
    }
    return this.instance;
  }
}

// 使う側
const config1 = AppConfig.getInstance();
const config2 = AppConfig.getInstance();

console.log(config1 === config2); // true(同じインスタンス)
TypeScript

ここでのポイントは、

  • コンストラクタが private なので、new AppConfig(...) は外からできない
  • 代わりに static getInstance() 経由でしかインスタンスを取れない
  • getInstance() の中で「まだなければ作る・あればそれを返す」を管理している

ということです。

「勝手に new されると困るから、作り方をクラス自身が管理する」
そのための鍵が private constructor です。

なぜ private にすることが大事なのか

もしコンストラクタが public のままだと、
どこからでも new AppConfig(...) できてしまいます。

そうなると、

  • 2つ目、3つ目のインスタンスが勝手に作られる
  • 「本当に1つだけ」という前提が崩れる
  • バグの原因になる(設定がバラバラになる、など)

private constructor にすることで、

「インスタンスの数や作り方を、クラス自身が完全にコントロールできる」

ようになります。

用途2:ファクトリメソッドで「作り方」を制御したい

直接 new させず、「こういう作り方だけ許す」と決めたい

例えば、日付を表すクラスを考えます。

class MyDate {
  private constructor(
    public readonly year: number,
    public readonly month: number,
    public readonly day: number
  ) {}

  static fromYmd(ymd: string): MyDate {
    const [y, m, d] = ymd.split("-").map(Number);
    return new MyDate(y, m, d);
  }

  static today(): MyDate {
    const now = new Date();
    return new MyDate(
      now.getFullYear(),
      now.getMonth() + 1,
      now.getDate()
    );
  }
}
TypeScript

ここでは、

  • コンストラクタは private なので、new MyDate(2024, 1, 1) は外からできない
  • 代わりに fromYmd("2024-01-01")today() といった
    「意味のある作り方」だけを公開している

という設計になっています。

使う側はこうなります。

const d1 = MyDate.fromYmd("2024-01-01");
const d2 = MyDate.today();
TypeScript

何がうれしいのか

private constructor にしておくと、

  • 不正な値で new されるのを防げる
    (例えば month に 13 を渡される、など)
  • 「このクラスはこういう作り方をしてほしい」という意図を
    static メソッドの名前で表現できる
  • 将来、内部表現を変えても、外側の呼び出し方を変えずに済む

といったメリットがあります。

「コンストラクタを隠して、“意味のある作り方”だけを公開する」
これが、private constructor+ファクトリメソッドの典型的な使い方です。

用途3:継承させたくない・ユーティリティクラスにしたい

「インスタンス化も継承もさせない」ユーティリティクラス

例えば、純粋なユーティリティクラスを作りたいとします。

class MathUtil {
  private constructor() {} // インスタンス化禁止

  static clamp(value: number, min: number, max: number): number {
    return Math.min(max, Math.max(min, value));
  }

  static isEven(value: number): boolean {
    return value % 2 === 0;
  }
}

const x = MathUtil.clamp(10, 0, 5); // OK
// const m = new MathUtil(); // エラー:コンストラクタが private
TypeScript

ここでの意図は、

  • このクラスは「インスタンスを持つ意味がない」
  • すべて static メソッドとして使ってほしい
  • だから new されると困る

というものです。

private constructor にしておくことで、

「このクラスは“インスタンス化される前提ではない”」

という設計意図を、コードで強制できます。

継承もさせたくない場合

TypeScript では final のようなキーワードはありませんが、
private constructor を使うと、継承も事実上できなくなります。

class Base {
  private constructor() {}
}

// class Child extends Base {} // エラー:Base のコンストラクタにアクセスできない
TypeScript

「このクラスは継承されることを想定していない」
という意思表示にもなります。

設計の視点:いつ private constructor を使うべきか

キーワードは「自由に new されると困るか?」

private constructor を使うか迷ったら、
自分にこう問いかけてみてください。

「このクラスは、どこからでも自由に new されていいだろうか?」

もし、

  • インスタンスが1つだけであることが前提
  • 作り方にルールがある(必ずバリデーションしたい、など)
  • インスタンス化されること自体に意味がない(ユーティリティ)
  • 継承されると設計が崩れる

といった事情があるなら、
private constructor を検討する価値があります。

「コンストラクタを公開する=作り方を自由にする」ということ

コンストラクタを public のままにしておく、というのは、

「このクラスは、どこからどういう値で new されてもいいです」

と宣言しているのと同じです。

それで問題ないクラスもたくさんありますが、
そうでないクラスもあります。

private constructor は、

「このクラスの作り方は、クラス自身が管理する」

という設計を選ぶためのスイッチだと思ってください。

まとめ:private constructor を自分の言葉で整理すると

最後に、あなた自身の言葉でこうまとめてみてください。

private constructor は、

  • クラスの外から new できなくするための仕組み
  • シングルトンのように「インスタンスを1つだけ」にしたいとき
  • ファクトリメソッド経由で「作り方を制御」したいとき
  • ユーティリティクラスのように「インスタンス化させたくない」とき
  • 継承させたくないクラスを作りたいとき

に使うと、設計がスッキリする。

今あなたが書いているクラスの中で、

「これは勝手に new されると困るな」
「本当は static メソッドだけで使ってほしいな」
「インスタンスは1個だけでいいのにな」

と感じるものがあれば、
一度 private constructor を試してみてください。

その瞬間から、
「ただのクラス」だったものが、「作り方までデザインされたクラス」
に変わっていきます。

タイトルとURLをコピーしました