Kanazawa.processではテスト駆動開発入門を読んで、テスト駆動開発を学びました。その本でのテスト駆動開発のサンプルとして挙がっていたテーマは複数通貨のMoneyを作るというものでした。
Dollarという米ドルを表現するオブジェクトを作り、Francというスイス・フランを表すオブジェクトを作り、そしてMoneyというオブジェクトに統合していくというリファクタリングの過程を経ています。
これをジェネリクスを使って設計したらどうなるでしょうか。
public abstract class Money<T extends Money<T>> {
public abstract T add(Money<?> m);
}
Moneyオブジェクトに演算用のメソッドadd()を定義します。引き算や掛け算など要求に合わせて各種用意するといいでしょう。
Money<T extends Money<T>>
という型変数の宣言方法は自己言及するジェネリクスの稿で
取り上げた手法です。add()の戻り型をTとしておくことで具象型自身の型を返させることができます。
これをDollarやFrancでオーバーライドして実装します。
public class Dollar extends Money<Dollar> {
@Override
public Dollar add(Money<?> m) {
// ...
}
}
こうすることで、Dollarのadd()はDollar型で返るわけです。
しかし、これだけだとDollarやFrancといった通貨単位ごとにadd()の実装を施さねばなりません。
protectedなファクトリーメソッドを用意して、オブジェクトの生成を具象型に委譲することで
スーパークラスであるMoney型で統一的にadd()を記述することができます。
public abstract class Money<T extends Money<T>> {
/** 為替レート */
Map<Class<? extends Money<?>>, Map<Class<? extends Money<?>>, Double>> rateMap;
/** 値 */
protected double value;
/** 加算メソッドの共通実装 */
public T add(Money<?> m) {
double rate = rateMap.get(this.getClass()).get(m.getClass());
return getInstance(this.value + m.value * rate);
}
/** 具象型の生成 */
protected abstract T getInstance(double value);
/** コンストラクタ */
protected Money(double value) {
this.value = value;
}
}
こうした手法で通貨単位ごとに型を作って何が嬉しいかと言えば、ドル建てである場所にスイス・フランの型が紛れ込んだ場合に
コンパイルエラーにすることができるという点です。
設計にはいろんな手法がありますし、それぞれにメリット、デメリットがあるので要求に合わせて柔軟に選べるようにしたいものですね。
投稿日時 : 2009年5月19日 0:54