🏭

abstract factoryパターンで点数管理システムを作ってみた

2022/11/09に公開約4,000字

Gof デザインパターンの abstract factory パターンで学校のテストの点数を管理するシステムを TypeScript を使用して実装してみました。
abstract factory パターンは生成に関するデザインパターンの一つで、関連したオブジェクトを都度インスタンスを生成することなく使用できます。
このように実装することで、以下のようなメリットがあります。
・使用する対象を変更しやすい
・機能を追加/変更した時の影響箇所を少なくできる

下記が今回作成したコードです。

enum Subject {
  Math,
  Science,
}

interface Score {
  id: number;
  score: number;
}

abstract class SubjectFactory {
  protected scores: { [id: number]: number } = {};

  abstract addScore(score: Score): void;
  abstract updateScore(score: Score): void;
  abstract getAverage(): number;
  abstract getScore(id: number): number;
}

class MathFactory extends SubjectFactory {
  addScore({ id, score }: Score) {
    if (id in this.scores) {
      throw new Error("そのIDは既に存在しています");
    }
    return (this.scores[id] = score);
  }

  updateScore({ id, score }: Score) {
    if (!(id in this.scores)) throw new Error("Idが存在しません");
    return (this.scores[id] = score);
  }

  getAverage(): number {
    let sum = 0;
    for (let key in this.scores) {
      sum += this.scores[key];
    }
    return sum / Object.keys(this.scores).length;
  }

  getScore(id: number): number {
    return this.scores[id];
  }
}

class ScienceFactory extends SubjectFactory {
  addScore({ id, score }: Score) {
    if (id in this.scores) {
      throw new Error("そのIDは既に存在しています");
    }
    return (this.scores[id] = score);
  }

  updateScore({ id, score }: Score) {
    if (!(id in this.scores)) throw new Error("Idが存在しません");
    return (this.scores[id] = score);
  }

  getAverage(): number {
    let sum = 0;
    for (let key in this.scores) {
      sum += this.scores[key];
    }
    return sum / Object.keys(this.scores).length;
  }

  getScore(id: number): number {
    return this.scores[id];
  }
}

function createSubjectFactory(subject: Subject): SubjectFactory {
  switch (subject) {
    case Subject.Math:
      return new MathFactory();
    case Subject.Science:
      return new ScienceFactory();
    default:
      throw new Error("教科が存在しません");
  }
}

それぞれのクラス・メソッドの説明をします。

abstract class SubjectFactory {
  protected scores: { [id: number]: number } = {};

  abstract addScore(score: Score): void;
  abstract updateScore(score: Score): void;
  abstract getAverage(): number;
  abstract getScore(id: number): number;
}

SubjectFactory クラスでは、関連しているオブジェクトの構造を定義しています。
この構造に従って作ることによって、利用するときに自分が何を扱っているかを気にしなくても済むようになります。

class MathFactory extends SubjectFactory {
  addScore({ id, score }: Score) {
    if (id in this.scores) {
      throw new Error("そのIDは既に存在しています");
    }
    return (this.scores[id] = score);
  }

  updateScore({ id, score }: Score) {
    if (!(id in this.scores)) throw new Error("Idが存在しません");
    return (this.scores[id] = score);
  }

  getAverage(): number {
    let sum = 0;
    for (let key in this.scores) {
      sum += this.scores[key];
    }
    return sum / Object.keys(this.scores).length;
  }

  getScore(id: number): number {
    return this.scores[id];
  }
}

class ScienceFactory extends SubjectFactory {
  addScore({ id, score }: Score) {
    if (id in this.scores) {
      throw new Error("そのIDは既に存在しています");
    }
    return (this.scores[id] = score);
  }

  updateScore({ id, score }: Score) {
    if (!(id in this.scores)) throw new Error("Idが存在しません");
    return (this.scores[id] = score);
  }

  getAverage(): number {
    let sum = 0;
    for (let key in this.scores) {
      sum += this.scores[key];
    }
    return sum / Object.keys(this.scores).length;
  }

  getScore(id: number): number {
    return this.scores[id];
  }
}

MathFactory クラスと ScienceFactory クラスは SubjectFactory で定義された構造に基づいて作成しています。

function createSubjectFactory(subject: Subject): SubjectFactory {
  switch (subject) {
    case Subject.Math:
      return new MathFactory();
    case Subject.Science:
      return new ScienceFactory();
    default:
      throw new Error("教科が存在しません");
  }
}

createSubjectFactory 関数で受け取った引数から対応しているクラスのインスタンスを返しています。
このようにすることで、新しく機能を追加しても他に影響を与えにくくしています。

Discussion

ログインするとコメントできます