Help us understand the problem. What is going on with this article?

JavaでFlyweightパターン

はじめに

GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。

Flyweightパターン

Flyweightとは

ボクシングの軽量級である「フライ級」のことです。
軽量級の意味することは「軽さ」であり、メモリの消費量が少ないことを指します。
オブジェクトを生成するにはnewを行い、メモリを確保する(インスタンス化)必要がありますが、多くのオブジェクトを生成するとメモリを多く消費してしまい、処理の速度が遅くなってしまします。
このような問題を解決するためには、すでにnewしたインスタンスに関しては繰り返し使用することが望ましいでしょう。
このようにオブジェクトをできるだけnewせずに共有させることでメモリの消費量を抑制するパターンのことをFlyweightパターンと言います。

登場人物

Flyweightパターン使用するのは以下のクラス図に登場するクラスです。
image.png

実装クラス

  • Flyweight
    共有して利用するクラスを表します。
    実装すべきメソッドなどは特にありませんので、難しい点はありません。

  • FlyweightFactory
    Flyweightを生成するための工場役となるクラスです。
    この工場役を通してFlyweight役を作るとインスタンスを共有できるメソッドを持ちます。
    共有するインスタンスを格納するpoolフィールドと、Flyweightを取得するためのgetFlyewightメソッドを持ちます。

  • Client
    FlyweightFactoryを利用してFlyweightを利用するクラスです。
    Flyweightと同様に実装すべきメソッドなどは特に定まられていないため、難しい点はありません。

具体例

具体例として、以下のクラスをもとに説明します。
image.png

実装クラス

  • Stampクラス
Stamp.java
package sample;

public class Stamp {
    // 文字
    private char charname;
    // 利用回数
    private int useCount = 0;
    // 生成回数
    private int newCount = 0;

    public int getUseCount() {
        return useCount;
    }

    public void setUseCount(int useCount) {
        this.useCount = useCount;
    }

    public int getNewCount() {
        return newCount;
    }

    public void setNewCount(int newCount) {
        this.newCount = newCount;
    }

    // コンストラクタ
    public Stamp(char charname) {
        this.charname = charname;
    }

    // 文字を表示する
    public void print() {
        System.out.println("charname:" + this.charname);
    }
}

Stampクラスは共有して利用されるFlyweight役となるクラスです。
文字charnameを受け取って生成され、printメソッドで文字を表示します。
また、利用回数(useCount)と生成回数(newCount)が分かりやすいようにフィールドを持たせていますが、必須ではありません。

  • StampFactoryクラス
StampFactory.java
package sample;

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

public class StampFactory {
    // 既に生成したStampインスタンスを管理
    private HashMap<String, Stamp> pool = new HashMap<>();
    // Singletonパターン
    private static StampFactory singleton = new StampFactory();

    // コンストラクタ
    private StampFactory() {
    }

    // シングルトンインスタンスを取得
    public static StampFactory getInstance() {
        return singleton;
    }

    // Stampのインスタンス生成(共有)
    public synchronized Stamp getStamp(char charname) {
        // キー(文字)に紐づく値(Stampインスタンス)を取得する
        Stamp bc = pool.get("" + charname);
        // キー(文字)に紐づく値(Stampインスタンス)が取得できなかった場合
        if (bc == null) {
            // ここでStampのインスタンスを生成
            bc = new Stamp(charname);
            // newした回数をカウント
            bc.setNewCount(bc.getNewCount() + 1);
            // HashMapに格納
            pool.put("" + charname, bc);
        }
        // newの有無にかかわらず利用した回数をカウント
        bc.setUseCount(bc.getUseCount() + 1);
        return bc;
    }

    // HashMapが管理しているStampインスタンスを全件出力
    public void printAllPool() {
        for (Entry<String, Stamp> entry : pool.entrySet()) {
            System.out.println(
                    entry.getKey() + " : " + entry.getValue().getUseCount() + " : " + entry.getValue().getNewCount());
        }
    }
}

Stampクラスを生成する工場であるFlayweightFactory役となるクラスです。
生成されたインスタンスを管理するためのMapとしてpoolフィールドと、ここではSingletonパターンを適用しているため自身を表すsingletonフィールドを持ちます。
このクラスの利用方法として外部からはまずgetInstanceメソッドを呼び出すことで自身を表すStampFactoryインスタンスを返却します。
そして返却されたStampFactoryインスタンスに対して、charnameを引数としてgetStampメソッドを呼び出します。
引数として渡されたcharnameをキーとしたStampインスタンスが既に生成されている場合にはpoolから取得しますが、インスタンスがまだ生成されていない場合にはインスタンスを生成してpoolに格納します。
また、インスタンスを生成した場合にはnewCountに+1し、インスタンスの生成有無にかかわらずにuseCountを+1することで、各Stampインスタンスの生成回数と利用回数をカウントします。
また、poolに格納しているStampインスタンスを全件出力するメソッドとしてprintAllPoolを実装しています。
出力する内容は「キーとなる文字:利用回数:生成回数」です。

実行クラス

  • Mainクラス
Main.java
package sample;

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        // Stampインスタンスの準備
        StampFactory factory = StampFactory.getInstance();
        ArrayList<Stamp> stamps = new ArrayList<>();
        stamps.add(factory.getStamp('a'));
        stamps.add(factory.getStamp('b'));
        stamps.add(factory.getStamp('c'));
        stamps.add(factory.getStamp('f'));
        stamps.add(factory.getStamp('e'));
        stamps.add(factory.getStamp('a'));
        stamps.add(factory.getStamp('b'));
        stamps.add(factory.getStamp('c'));
        stamps.add(factory.getStamp('d'));
        stamps.add(factory.getStamp('f'));
        stamps.add(factory.getStamp('a'));
        for (Stamp s : stamps) {
            s.print();
        }
        System.out.println("-------------------------------");
        System.out.println("charname : useCount : newCount");
        // HashMapで管理されているStampインスタンスを全件出力
        factory.printAllPool();
    }
}

Flyweight、FlyweightFactoryを利用するClient役となるクラスです。
StampFactoryクラスのstaticメソッドであるgetInstanceメソッドを呼び出してstampFactoryインスタンスを取得し、取得したstampFactoryインスタンスに対してgetStampメソッドを呼び出すことで、poolフィールドにstampインスタンスを格納していきます。
最後にprintAllPool()を呼び出すことでpoolフィールドを全件出力しています。

実行結果

Main.javaを実行した結果は以下になります。
useCountが1より大きい文字に関しても、newCountは1回のみであり、インスタンスが再利用されていることが分かります。

実行結果
charname:a
charname:b
charname:c
charname:f
charname:e
charname:a
charname:b
charname:c
charname:d
charname:f
charname:a
-------------------------------
charname : useCount : newCount
a : 3 : 1
b : 2 : 1
c : 2 : 1
d : 1 : 1
e : 1 : 1
f : 2 : 1

メリットとデメリット

Flyweightパターンを利用することでオブジェクトをnewする回数を削減することができ、メモリを節約することができます。
一方デメリットとしてはpoolに格納されたオブジェクトはガベージコレクションの対象とはならず、メモリ上に残り続けてしまうため、メモリが不足しないように意図的に管理する必要があることがあげられます。

まとめ

インスタンスを共有することでメモリの消費を抑制するFlyweightパターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。

また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。

参考文献

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした