[Design pattern] 2-5. フライウェイトパターン(Flyweight pattern)


Study / Design pattern    作成日付 : 2021/10/29 19:48:27   修正日付 : 2021/10/29 19:48:27

こんにちは。明月です。


この投稿はデザインパターンのフライウェイトパターン(Flyweight pattern)に関する説明です。


フライウェイトパターン(Flyweight pattern)という英語の意味は軽量化するという意味です。なので、インスタンスの生成を最小化してメモリの使用をできれば節約する方法です。

構造パターンのシングルトンバージョンだと思えば良いでしょう。でもsingletonみたいにstaticを利用することではなく、普通のMap(Dictionary)を利用します。


Reference - https://en.wikipedia.org/wiki/Flyweight_pattern

#pragma once
#include <stdio.h>
#include <iostream>
#include <map>
using namespace std;
// INodeインターフェース
class INode {
public:
  // 抽象関数
  virtual void print() = 0;
  virtual ~INode() {};
};
// Nodeインスタンスを生成するBuilderクラス
class NodeBuilder {
private:
  // インラインクラス(外部から参照できない。)
  // INodeインターフェースを継承
  class Node : public INode {
  private:
    // メンバー変数
    char data;
    int count = 0;
  public:
    // コンストラクタ
    Node(char data) {
      // dataを格納
      this->data = data;
    }
    // 出力関数
    virtual void print() {
      // 出力、dataは入力した値で、countはBuilderから呼び出すたびに増加したcountだ。
      cout << this->data << " Node counting - " << this->count << endl;
    }
    // カウント関数
    void counting() {
      // count変数の値を1増加
      this->count++;
    }
  };
  // flyweightマップ
  map<char, Node*> flyweight;
public:
  // Nodeクラスを生成する関数
  Node* getNode(char data) {
    // data値でflyweightマップからdataをキーにするNodeインスタンスがあるかどうか確認
    if (this->flyweight.find(data) == this->flyweight.end()) {
      // なければインスタンスを生成する。
      this->flyweight.insert(make_pair(data, new Node(data)));
    }
    // flyweightマップでdataをキーでNodeインスタンスを取得
    Node* node = this->flyweight.at(data);
    // Nodeクラスのcountを1増加
    node->counting();
    // リターン
    return node;
  }
  // デストラクタ
  ~NodeBuilder() {
    // flyweightマップにあるインスタンスをすべてメモリ解除
    for (map<char, Node*>::iterator ptr = this->flyweight.begin(); ptr != this->flyweight.end(); ptr++) {
      // メモリ解除
      delete ptr->second;
    }
  }
};
// 実行関数
int main() {
  // NodeBuilderインスタンス生成
  NodeBuilder builder;
  // aのキーでNodeインスタンスを取得する。
  INode* node = builder.getNode('a');
  // 出力
  node->print();
  // bのキーでNodeインスタンスを取得する。
  node = builder.getNode('b');
  // 出力
  node->print();
  // aのキーでNodeインスタンスを取得する。
  node = builder.getNode('a');
  // 出力
  node->print();

  return 0;
}


BuilderというクラスでgetNodeからNodeインスタンスを取得します。

そのことでaのキーで取得した時にはcountが2になりました。その意味はaを二回呼び出しましたが、インスタンスは同じという意味ですね。つまり、二回目から呼び出したらインスタンスを新しく生成しなくて、mapに同じインスタンスを取得することがフライウェイトパターンです。

import java.util.HashMap;
import java.util.Map;

// インターフェース
interface INode {
  // 抽象メソッド
  void print();
}

// INodeを継承したANodeクラス
class ANode implements INode {
  // 抽象メソッドを再定義する。
  public void print() {
    // コンソールに出力
    System.out.println("The memory address of ANode class - " + super.hashCode());
  }
}

// INodeを継承したbNodeクラス
class BNode implements INode {
  // 抽象メソッドを再定義する。
  public void print() {
    // コンソールに出力
    System.out.println("The memory address of BNode class - " + super.hashCode());
  }
}

// ファクトリークラス
class NodeFactory {
  // flywieghtパターン
  private Map<Class<? extends INode>, INode> flyweight = new HashMap<>();

  // Nodeインスタンスを取得
  public INode getNode(Class<? extends INode> clz) {
    // flywieght変数のマップに格納されてない場合
    if (!flyweight.containsKey(clz)) {
      // パラメータタイプがANodeならANodeのインスタンスを生成して格納。
      if (clz == ANode.class) {
        flyweight.put(clz, new ANode());
      }
      // パラメータタイプがBNodeならBNodeのインスタンスを生成して格納。
      else if (clz == BNode.class) {
        flyweight.put(clz, new BNode());
      } else {
        // その以外は例外処理
        throw new UnsupportedOperationException();
      }
    }
    // 一回に生成されたものは再使用
    return flyweight.get(clz);
  }
}

// 実行クラス
class Program {
  // 実行関数
  public static void main(String[] args) {
    // ファクトリークラス生成
    var factory = new NodeFactory();

    // ANodeインスタンス取得
    factory.getNode(ANode.class).print();
    // BNodeインスタンス取得
    factory.getNode(BNode.class).print();
    // ANodeインスタンス取得
    factory.getNode(ANode.class).print();
  }
}


実はフライウェイトパターンは上のファクトリーメソッドパターンとともによく使います。Factoryでインスタンスを生成せずに一回生成されたインスタンスは再使用ということです。

でも、ファクトリ―メソッドパターンなのでClassを追加するたびにFactory関数を修正しなければならないですね。

using System;
using System.Collections.Generic;
// IDaoインターフェース
interface IDao
{
  // 抽象関数
  void Select();
}
// IDaoインターフェースを継承したADao
class ADao: IDao
{
  // 関数再定義
  public void Select()
  {
    // コンソールに出力
    Console.WriteLine("ADao was selected!");
  }
}
// IDaoインターフェースを継承したBDao
class BDao : IDao
{
  // 関数再定義
  public void Select()
  {
    // コンソールに出力
    Console.WriteLine("BDao was selected!");
  }
}
// FactoryDaoクラス
class FactoryDao
{
  // フライウェイトパターンのDictionary
  private Dictionary<Type, IDao> flyweight = new Dictionary<Type, IDao>();
  // Daoインスタンスを取得
  public T GetDao<T>() where T : IDao
  {
    // ジェネリックタイプでフライウェイトディクショナリにインスタンスがあるかどうか確認
    if (!flyweight.ContainsKey(typeof(T)))
    {
      // なければReflection機能を利用してインスタンス生成
      flyweight.Add(typeof(T), (IDao)Activator.CreateInstance(typeof(T)));
    }
    // インスタンスリターン
    return (T)flyweight[typeof(T)];
  }
}

class Program
{
  // 実行関数
  static void Main(string[] args)
  {
    // FactoryDaoインスタンス生成
    var factory = new FactoryDao();
    // ADaoインスタンスを受け取ってSelect関数を実行
    factory.GetDao<ADao>().Select();
    // BDaoインスタンスを受け取ってSelect関数を実行
    factory.GetDao<BDao>().Select();
    // 任意のキーを押してください
    Console.WriteLine("Press any key...");
    Console.ReadKey();
  }
}


上のれ例ではJavaの例と似ていますが、FactoryDao中をReflectionとGeneric機能を利用してインスタンスを取得することにしました。

このパターンどのところで使うかと思えば、ORMフレームワークのDaoを取得する関数で使う方法です。特にSpringで依存性注入でDaoを取得する時に、フレームワークでは上みたいな構造でインスタンスを取得することです。

つまり、一回に生成されたインスタンスは再使用しようという意味ですね。


ここまでデザインパターンのフライウェイトパターン(Flyweight pattern)に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

最新投稿