Timeオブジェクト
予約支援のためのスケジューリングアプリケーションを作成します。このアプリケーションで使用するTimeクラスは、時間(24時間制の時計を想定)と分で表現された時刻をカプセル化します。
public class Time { private byte hour; private byte minute; public Time(byte hour, byte minute) { this.hour = hour; this.minute = minute; } public byte hour() { return hour; } public byte minute() { return minute; } public String toString() { return hour + ":"+ minute; } }
このアプリケーションでは、何百万件にも及ぶような膨大な数の予約をサポートする必要があるので、必要となるメモリが心配のタネです。ただし実際には、営業日の1件1件の予約の開始時刻は通常、正時きっかりか、または正時から15分間隔です(0分、15分、30分、45分の4種類)。Flyweightパターンを使用して一意なTimeインスタンスのみを格納するようにすれば、標準的なtimeオブジェクトの数を最大96個(24時間×4種類)まで減らすことができます。この予約アプリケーションはその他の時刻もサポートしますが、予測可能な使い方ができる部分では、Flyweightを使用するとオブジェクト生成とメモリ使用量が最小限に抑えられます。
Flyweightを活用するためのカギは、ファクトリメソッドを使用してオブジェクトのインスタンス化を制御する点にあります。ファクトリメソッドの役割は単にオブジェクトを生成することであり、指定された入力条件に応じて適切な型のオブジェクトを返します。TimeFactoryクラスを設計して、簡単な単体テストを作成してみます。
import static org.junit.Assert.*; import org.junit.*; public class TimeFactoryTest { private static final byte HOUR = 23; private static final byte MINUTE = 59; @Test public void create() { Time time = TimeFactory.create(HOUR, MINUTE); assertEquals("23:59", time.toString()); } }
このテストを通るコードは簡単です。
public class TimeFactory { public static Time create(byte hour, byte minute) { return new Time(hour, minute); } }
さらに、次のような別のテストを用意します。
@Test public void reuseOfMemory() { Time time1 = TimeFactory.create(HOUR, MINUTE); Time time2 = TimeFactory.create(HOUR, MINUTE); assertSame(time1, time2); }
このテストで確認したいのは、同じ時間と分を表す2つのtimeオブジェクトを生成した場合、どちらもメモリ内では同じオブジェクトを指すということです。この例のJUnitのassertSame()メソッドは、次のように指定した場合と同じことです。
assertTrue(time1 == time2);
前述のTimeFactoryクラスを使用した場合、create()メソッドは別々のTimeインスタンスを返すため、このテストは失敗します。reuseOfMemoryテストに合格するためには、TimeFactoryの実装を次のように変更します。
import java.util.*; public class TimeFactory { private static Map<String,Time> times = new HashMap<String,Time>(); public static Time create(byte hour, byte minute) { String key = hour + ":"+ minute; Time time = times.get(key); if (time == null) { time = new Time(hour, minute); times.put(key, time); } return time; } }
インスタンス化をファクトリのcreate()メソッドにカプセル化したので、Timeオブジェクトが生成されるタイミングを制御できます。まず最初に、時間と分を連結して時刻を表す文字列とし、一意のキーを作成します。このキーを使用して、timesという新しいHashMapからエントリを取り出します(エントリが存在する場合)。エントリが存在するときは、そのエントリが返されます。エントリが存在しないときは新しいTimeオブジェクトを生成し、作成したキーでオブジェクトをtimesに格納します。
これで完成です。このように、Flyweightの実装は単純明快です。主な欠点は、次のような点です。
- Timeオブジェクトのインスタンス化は、すべてファクトリ経由で行う必要があります。ファクトリでインスタンス化を行わない場合、Flyweightのメリットを失います。
- ファクトリの使用には、追加のメソッド呼び出しと、ハッシュテーブル検索というオーバーヘッドがあります。Javaでは、ごくまれにこのことがパフォーマンス上の問題となります。
