🐝

【TS】今さら聞けないフライウェイトパターン

2021/02/23に公開

はじめに

今回はフライウェイトパターン(Flyweight Pattern)について解説します。
インスタンスを使い回すことで、メモリ消費を抑えるためにデザインパターンです。

フライウェイトパターンとは?

TECHSCOREさんの解説があります。

以下、引用です。

例えば、ホームページの背景に使う小さな画像は、背景で表示される回数分ネットワークごしにやり取りされるわけではありません。 普通は、画像を一回取得し、「その画像」を並べて表示するわけです。

Flyweight パターンとは、このように、同じインスタンスを共有することで、無駄なインスタンスを生成しないようにして、 プログラム全体を軽くすることを目的としたパターンなのです。

ポイントは 「同じインスタンスを共有することで、無駄なインスタンスを生成しない」 ということです。
具体的な例を見ていきましょう。

例題

入力された文字列を一文字ずつオリジナルフォントの文字に変更して表示するサービスを開発しました。
オリジナルフォントの一つである「ファニーなフォント」(以下ファニーフォント)を表示するため、以下のようにFunnyCharクラスを作りました。

FunnyChar.ts
/**
 * ファニーフォント クラス
 */
class FunnyChar { 
  // 表示する文字
  private _char: string = '';

  constructor(char: string) {
    this._char = char;
  }

  // 表示処理
  public disp() {
    // ここではシンプルにconsole.logしているが
    // 実際はフォントを独自のものに差し替えて画面に表示している
    console.log(this._char);
  }
}

入力された文字列を一文字ずつ分割して表示する場合、以下のように記載するかと思います。

index.ts
const value: string = 'よのなかねかおかおかねかなのよ';

// 入力された文字を一文字ずつFunnyCharに変換してdisp()
for(let i = 0; i < value.length; i++) {
  const fc: FunnyChar = new FunnyChar(value[i]);
  fc.disp();
}

出力

よ
の
な
か
ね
か
お
か
お
か
ね
か
な
の
よ

実はこの入力は回文になっているので、同じ文字が複数回出現しています。
例えば「か」は5回も登場しています。
当然「か」という文字のFunnyCharインスタンスも5回生成されますが、いずれのdisp()の出力も同じものです。
これはメモリを無駄に消費しているといえます。

フライウェイトパターンで実装

そこでフライウェイトパターンを導入します。
具体的に何をするかというと、インスタンスを生成するFactoryを定義します。
以下の例ではFunnyCharインスタンスを生成するFunnyCharFactoryを定義しています。

FunnyCharFactoryはシングルトンパターンで記載されています。
シングルトンパターンについては、下記記事で解説しています。

https://zenn.dev/nekoniki/articles/b05c0f1297f301d3bd63

FunnyCharFactory
/**
 * ファニーフォント Factory
 */
class FunnyCharFactory {
  // FunnyCharのマップ
  private _funnyCharMap: Map<string, FunnyChar> = new Map<string, FunnyChar>();
  // インスタンス
  private static _instance: FunnyCharFactory;
  // Singletonパターンにしてインスタンス生成をできなくする
  private constructor() {}

  public static get instance(): FunnyCharFactory {
    if(!this._instance) {
      this._instance = new FunnyCharFactory();
    }  
    return this._instance;
  }

  // FunnyCharインスタンスを取得する
  public getFunnyChar(char: string): FunnyChar {
      // Map内にインスタンスがない(=はじめて生成するインスタンス)
      if(!this._funnyCharMap.has(char)) {
          // Mapに追加
          this._funnyCharMap.set(char, new FunnyChar(char));
      }

      // Mapからインスタンスを取り出し返却する
      // 余分なインスタンスを生成しない
      return this._funnyCharMap.get(char);
  }
}

export default FunnyCharFactory;

ポイントはFunnyCharMapで管理していることです。
こうすることで同一文字に対応するインスタンスを複数回生成することがなくなるため、メモリを余分に消費することがなくなります。

この場合の呼び出し方は以下のようになります。

index.ts
const value: string = 'よのなかねかおかおかねかなのよ';

// FunnyCharFactoryのインスタンス
const factory: FunnyCharFactory = FunnyCharFactory.instance;

// 入力された文字を一文字ずつFunnyCharに変換してdisp()
for(let i = 0; i < value.length; i++) {
  const fc: FunnyChar = factory.getFunnyChar(value[i]);
  fc.disp();
}

まとめ

今回はTypescriptでデザインパターンの一つである Flyweight Pattern について紹介しました。
特徴として「インスタンスを共有」することで、メモリの消費を抑える点が挙げられます。

何回も同じインスタンスを生成する可能性があり、かつその負荷が大きくなるようなロジックの場合は導入してみるといいかなと思います。

Discussion