Java : Flyweight パターン (図解/デザインパターン)
Flyweight パターンとは、GoF によって定義されたデザインパターンの1つです。
同じ内容のオブジェクトを再利用して、リソースを節約するという設計です。
本記事では、Flyweight パターンを Java のコード付きで解説していきます。
デザインパターン(GoF) 関連記事
- 生成に関するパターン
- 振る舞いに関するパターン
概要
Flyweight パターンとは、内容の同じオブジェクトが必要となる場合に、
- 複数のオブジェクトは作らない
- 1つのオブジェクトを再利用(共有) してリソースを節約する
という設計です。
例えば、
- 大きなオブジェクトを使う
- 小さなオブジェクトでも大量に生成する
といったケースで有用でしょう。
Flyweight は、日本語的に発音すると「フライウェイト」となります。
意味は「フライ級」で、ボクシングなどの格闘技で使われる階級の1つです。階級のなかでも体重が軽めの階級ですね。
【 Flyweight パターンのイメージ図 】
Flyweight パターンでは、クライアントは ファクトリ を介してオブジェクトを取得します。
オブジェクトを直接生成しないのがポイントですね。
一方、ファクトリは、
- 必要に応じてオブジェクトを生成
- もし、同じ内容のオブジェクト取得要求がきたら、作成済みのオブジェクトを返す
という感じになります。
Flyweight パターンは、特に 不変オブジェクト と相性のよいパターンです。
不変オブジェクトとは簡単にいうと、オブジェクトを生成した後に、内容の変更がいっさいできないオブジェクトのことです。
詳しくは下記の記事で解説していますので、そちらもご参照いただけたら幸いです。
- 関連記事 : 不変オブジェクト(イミュータブル) とは
クラス図とシーケンス図
上の図は、Flyweight パターンの一般的なクラス図です。
右側の Flyweight クラスが、再利用の対象となるオブジェクトになります。
FlyweightFactory クラスの pool フィールドは配列になっていますが、
といった コレクション を使うことも多いと思います。
また、getFlyweight メソッドには、オブジェクトの同一性を判断するためのパラメータが必要になるでしょう。
シーケンス図は次のようになります。
コード例
先述した Flyweight パターンのクラス図ですが、コード例を作るにあたり、もう少し具体的な名前に変更します。
それぞれのクラスは、
- ProductFactory → FlyweightFactory
- Product → Flyweight
に相当します。
Flyweight パターンを使わないケース
まずは、Product クラスを見ていきましょう。
このクラスのオブジェクトは 不変 になります。
public class Product {
private final String name;
private final int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
@Override
public String toString() {
return "商品 : %s (%d円)".formatted(name, price);
}
}
Product は "商品" という意味になります。
属性として、名前(name) と 値段(price) を持ちます。
この Product を、Flyweight パターンを使わないで … つまり new 演算子で生成すると次のようになります。
final var product1 = new Product("テーブル", 5000);
final var product2 = new Product("テーブル", 5000);
final var product3 = new Product("椅子", 2000);
// オブジェクトはすべて異なります。
System.out.println(product1 == product2); // false
System.out.println(product1 == product3); // false
System.out.println(product2 == product3); // false
System.out.println(product1); // 商品 : テーブル (5000円)
System.out.println(product2); // 商品 : テーブル (5000円)
System.out.println(product3); // 商品 : 椅子 (2000円)
内容の同じ Product でも、オブジェクト(インスタンス) は別になっていることが分かりますね。
オブジェクトが別ということは、それだけ余計にリソースを消費していることになります。
Flyweight パターンを使うケース
次は、Flyweight パターン を使う例を見てみましょう。
まずは、FlyweightFactory に相当する ProductFactory クラスを作ります。
public class ProductFactory {
private record Key(String name, int price) {
}
private final Map<Key, Product> pool = new HashMap<>();
public Product get(String name, int price) {
final var key = new Key(name, price);
if (pool.containsKey(key)) {
return pool.get(key);
} else {
final var product = new Product(name, price);
pool.put(key, product);
return product;
}
}
}
今回、pool フィールドは Map で管理することにしました。
Map のキーには、name と price パラメータを持つ レコードクラス を使います。
get メソッドでは、
- 内容の同じオブジェクトが pool に存在するか?
- 存在していれば、それを返す
- 存在していなければ、新しくオブジェクトを生成して pool に追加
という処理を行っています。
それでは、ProductFactory を使ってオブジェクトを取得してみましょう。
final var factory = new ProductFactory();
final var product1 = factory.get("テーブル", 5000);
final var product2 = factory.get("テーブル", 5000);
final var product3 = factory.get("椅子", 2000);
// "テーブル (5000円)" のオブジェクトは同じになります。
System.out.println(product1 == product2); // true
// "テーブル (5000円)" と "椅子 (2000円)" のオブジェクトは異なります。
System.out.println(product1 == product3); // false
System.out.println(product2 == product3); // false
System.out.println(product1); // 商品 : テーブル (5000円)
System.out.println(product2); // 商品 : テーブル (5000円)
System.out.println(product3); // 商品 : 椅子 (2000円)
無事に、同じ内容のオブジェクトは再利用されていることが確認できました。
注意点
FlyweightFactory の pool フィールドには注意が必要です。
pool フィールドは、内容の異なるオブジェクトが増えるほどに、だんだんとサイズが大きくなっていきます。
そして、減ることはありません。ガベージコレクション (GC) の対象にもなりません。
せっかく Flyweight パターンでリソースを節約しているのに、pool のサイズが巨大になっては本末転倒です。
例えば、
- ある程度のサイズになったら pool をクリアする
- WeakReference などの参照を使う
といった処理が必要になるかもしれません。
まとめ
Flyweight パターンとは、内容の同じオブジェクトが必要となる場合に、
- 複数のオブジェクトは作らない
- 1つのオブジェクトを再利用(共有) してリソースを節約する
という設計です。
ただ、最近はリソースが潤沢な環境も多いため、あまり出番はないかもしれません。
組み込みシステムなどのリソースが限られた環境では、必要になることもありそうです。
関連記事
- 標準APIにならう命名規則
- コメントが少なくて済むコードを目指そう
- シングルトン・パターンの乱用はやめよう
- メソッドのパラメータ(引数)は使う側でチェックしよう
- 不変オブジェクト(イミュータブル) とは
- 依存性の注入(DI)をもっと気軽に
- 不要になったコードはコメントアウトで残さずに削除しよう
- 簡易的な Builder パターン
- 読み取り専用(const) のインタフェースを作る
- 図解/デザインパターン一覧 (GoF)
- Abstract Factory パターン
- Adapter パターン
- Bridge パターン
- Builder パターン
- Chain of Responsibility パターン
- Command パターン
- Composite パターン
- Decorator パターン
- Facade パターン
- Factory Method パターン
- Interpreter パターン
- Iterator パターン
- Mediator パターン
- Memento パターン
- Observer パターン
- Prototype パターン
- Proxy パターン
- Singleton パターン
- State パターン
- Strategy パターン
- Template Method パターン
- Visitor パターン