Flyweightパターンの検証
さて、このパターンにはそれだけの価値があるでしょうか。簡単なアプリケーションクラスを作成して、Flyweightが割に合うかどうかを調べてみます。
import java.util.*; public class App extends Thread { private static Random random = new Random(); public static void main(String[] args) { new App().start(); } public void run() { int size = 1000000; List<Appointment> appointments = new ArrayList<Appointment>(size); for (int i = 0; i < size; i++) { appointments.add(createRandomAppt()); } System.out.println("created " + appointments.size()); while (true) { Thread.yield(); } } private Appointment createRandomAppt() { Time start = createRandomTime(); Time stop = createRandomTime(); // kind of bogus return new Appointment(start, stop); } private Time createRandomTime() { byte hour = (byte)random.nextInt(24); byte quartile = (byte)random.nextInt(4); byte minute = 0; switch (quartile) { case 0: minute = 0; break; case 1: minute = 15; break; case 2: minute = 30; break; case 3: minute = 45; break; } //return new Time(hour, minute); return TimeFactory.create(hour, minute); } }
本来ならば予約の終了時刻は開始時刻よりも後になるはずですが、このアプリケーションではその点を無視して、ランダムな開始時刻と終了時刻を設定した予約を多数作成しています。時刻は15分間隔に限定しています。少しテストするにはこれで十分です。実際のケースでは、通常の営業日の予約時刻はもっと大きい単位で制限されるでしょう。
createRandomTime()の最後の2行が、Timeインスタンスの生成方法を制御する行です。1回目に実行するときは、直接コンストラクタを使ってTimeを生成する下記の行のコメントを解除し、
return new Time(hour, minute);
ファクトリを使用する行はコメントアウトしました。
jconsole(Java5と6に同梱)を実行してアプリケーションをモニタしたところ、ヒープのプロファイリングによると、このアプリケーションには52~56MBのメモリが必要でした。
次に、ファクトリを使用する下記の行のコメントを解除して、Timeオブジェクトの生成方法を切り替えました。
return TimeFactory.create(hour, minute);
2回目にjconsoleを実行したとき、アプリケーションが使用したのは20~22MBでした。今回、Flyweightパターンを使用することで、30MBを超えるメモリが節約されたことになります。
メモリの最適化やパフォーマンスの最適化と同様、プロファイリングとテストは重要です。間違いを修正するのは簡単ですが、修正するときにも不具合を生じやすいのです。Flyweightなどのパターンを正しく使用することで、アプリケーションのメモリ使用量を簡単かつ大幅に改善できます。
