JavaでFlyweightパターン

# はじめに
[GoF](https://ja.wikipedia.org/wiki/ギャング・オブ・フォー_(情報工学))のデザインパターンを紹介している[『増補改訂版 Java言語で学ぶデザインパターン入門』](https://www.amazon.co.jp/増補改訂版Java言語で学ぶデザインパターン入門-結城-浩/dp/4797327030/ref=pd_lpo_sbs_14_t_0?_encoding=UTF8&psc=1&refRID=2ZE4GPYNN55JGDR5QMHP)を読んで、学んだ内容についてまとめます。

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

## 登場人物
Flyweightパターン使用するのは以下のクラス図に登場するクラスです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278934/3a0cb58d-fd5a-544a-04b5-c76be72b05e9.png)


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

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

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

## 具体例
具体例として、以下のクラスをもとに説明します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278934/bf6fe2f1-0d70-662e-a399-c427f1e28b8d.png)


### 実装クラス
- **Stampクラス**  

```java: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クラス**  

```java: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クラス**  

```java: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パターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。

- [Flyweightサンプルコード](https://github.com/masayuki777/study/tree/master/DesignPatterns/20_Flyweight)

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

- [[随時更新]Javaでデザインパターンまとめ](https://qiita.com/mk777/items/4e13505b654fc21c76a2)

# 参考文献
- [増補改訂版 Java言語で学ぶデザインパターン入門](https://www.amazon.co.jp/増補改訂版Java言語で学ぶデザインパターン入門-結城-浩/dp/4797327030/ref=pd_lpo_sbs_14_t_0?_encoding=UTF8&psc=1&refRID=2ZE4GPYNN55JGDR5QMHP)