はじめに
GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。
Flyweightパターン
Flyweightとは
ボクシングの軽量級である「フライ級」のことです。
軽量級の意味することは「軽さ」であり、メモリの消費量が少ないことを指します。
オブジェクトを生成するにはnewを行い、メモリを確保する(インスタンス化)必要がありますが、多くのオブジェクトを生成するとメモリを多く消費してしまい、処理の速度が遅くなってしまします。
このような問題を解決するためには、すでにnewしたインスタンスに関しては繰り返し使用することが望ましいでしょう。
このようにオブジェクトをできるだけnewせずに共有させることでメモリの消費量を抑制するパターンのことをFlyweightパターンと言います。
登場人物
Flyweightパターン使用するのは以下のクラス図に登場するクラスです。
実装クラス
-
Flyweight
共有して利用するクラスを表します。
実装すべきメソッドなどは特にありませんので、難しい点はありません。 -
FlyweightFactory
Flyweight
を生成するための工場役となるクラスです。
この工場役を通してFlyweight役を作るとインスタンスを共有できるメソッドを持ちます。
共有するインスタンスを格納するpool
フィールドと、Flyweightを取得するためのgetFlyewight
メソッドを持ちます。 -
Client
FlyweightFactory
を利用してFlyweightを利用するクラスです。
Flyweightと同様に実装すべきメソッドなどは特に定まられていないため、難しい点はありません。
具体例
実装クラス
- Stampクラス
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クラス
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クラス
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パターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。
また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。