= Guiceへのリファクタリング = == ヒドイコード == こういうのどこにでもあるでしょ!静的なユーティリティメソッドがいろんなものに依存していて、それもまた静的ときたもんだ。 {{{ public class PizzaUtilities { private static final int TIME_TO_PREPARE = 6; private static final int MAX_DISTANCE = 20; public static Order createOrder(List pizzas, Customer customer) { /** 訳注:配達すべき方向を取得する */ Directions directions = Geography.getDirections( PizzaStore.getStoreAddress(), customer.getDeliveryAddress()); /** 訳注:方向がわかんない、あるいは遠すぎる場合は例外 */ if (directions == null || directions.getLengthInKm() > MAX_DISTANCE) { throw new InvalidOrderException("Cannot deliver to , " + customer.getDeliveryAddress()); } /** 訳注:到着時刻を計算する。料理時間、オーブンの時間、配達地までの時間 */ int arrivalTime = TIME_TO_PREPARE + Oven.getCurrentOven().schedule(TIME_TO_PREPARE, pizzas) + directions.estimateTravelTime(); /** 訳注:納品書を作成 */ Invoice invoice = Invoice.create(pizzas, directions.getLengthInKm());     /** 訳注:注文オブジェクトを作成 */ return new Order(pizzas, invoice, arrivalTime, customer, directions); } } }}} == なんでこれが気に入らないのか? == Geography, PizzaStore, Oven, Invoiceクラスに静的に依存しているもんで、こいつらもひっくるめてテストをやらなきゃならない。結果として、 * PizzaStoreの初期化が遅かったりすると、テストも待たされてしまう。 * このメソッドをテストするためには、Geograpyのコードが必要だ。もし別のチームがそれを作っている最中だとしても。 * テストのsetUp()の中で、Oven.setCurrentOven()をセットすることを覚えておかければ。そうしないとテストは失敗してしまう。 * もし、Invoice.create()が外部サービス、例えば支払処理サービスなんかに依存してたりすると、そのサービスがなきゃテストできないじゃん。 == 同じメソッドの静的でないバージョン == 静的でないバージョンを作ってみよう。 {{{ public class PizzaServices { public Order createOrder(List pizzas, Customer customer) { return PizzaUtilities.createOrder(pizzas, customer); } } }}} == 静的な呼び出しを静的でない呼び出しに置き換える == PizzaUtilitiesを呼び出すコードを、PizzaServicesの注入に置き換えよう。 {{{ class OrderPizzaAction { public void order(HttpSession session) { Customer customer = session.getCurrentCustomer(); PizzaUtilities.createOrder(getPizzaSpecs(), customer); } ... } }}} 上を以下のように変える。 {{{ class OrderPizzaAction { private final PizzaServices pizzaServices; @Inject OrderPizzaAction(PizzaServices pizzaServices) { this.pizzaServices = pizzaServices; } public void order(HttpSession session) { Customer customer = session.getCurrentCustomer(); pizzaServices.createOrder(getPizzaSpecs(), customer); } } }}} OrderPizzaActionのコンストラクタを変える必要があるよ。 これは後回しにしたいっていうんなら、PizzaServicesを静的に注入することにしてもいいけど。 {{{ class OrderPizzaAction { @Inject public static PizzaServices pizzaServices; public void order(HttpSession session) { Customer customer = session.getCurrentCustomer(); pizzaServices.createOrder(getPizzaSpecs(), customer); } } }}} そんでもって、モジュールでは静的注入を指定しとこう。 {{{ class PizzaModule extends AbstractModule { protected void configure() { requestStaticInjection(OrderPizzaAction.class); } } }}} 静的注入は、静的から非静的にリファクタリングするあいだ、非常に便利に使えるんだ。 さて、ここまでで既にメリットが生まれた。OrderPizzaActionはGeograpy等々がなくてもテスト可能になった。 createOrderをオーバライドしたPizzaServicesのモックを作って、そいつを渡せばいいのだ。 == ロジックを非静的バージョンに変更する == PizzaUtilitiesにある実装ロジックをPizzaServicesに移動してしまおう。 PizzaUtilitiesには呼び出し転送メソッドを残しておくことで、こいつを利用している他の連中をは変更せずに済む。 {{{ public class PizzaUtilities { @Inject public static PizzaServices pizzaServices; public static Order createOrder(List pizzas, Customer customer) { return pizzaServices.createOrder(pizzas, customer); } } public class PizzaServices { private static final int TIME_TO_PREPARE = 6; private static final int MAX_DISTANCE = 20; public Order createOrder(List pizzas, Customer customer) { Directions directions = Geography.getDirections( PizzaStore.getStoreAddress(), customer.getDeliveryAddress()); ... return new Order(pizzas, invoice, arrivalTime, customer, directions); } } }}} PizzaUtilitiesにPizzaServicesを注入するため、これまた静的注入を指定することにしよう。 {{{ class PizzaModule extends AbstractModule { protected void configure() { requestStaticInjection(OrderPizzaAction.class); requestStaticInjection(PizzaUtilities.class); } } }}} == 非静的バージョンを注入しよう == さて、PizzaUtilitiesに注入することができた、今度はそいつ(PizzaServices)に注入することにしようか。 手近なものはOven.getCurrentOven()シングルトンだ。 こいつを注入できるようにするためには、モジュールでバインド指定する。 {{{ public class PizzaServices { private final Oven currentOven; @Inject public PizzaServices(Oven currentOven) { this.currentOven = currentOven; } public Order createOrder(List pizzas, Customer customer) { ... int arrivalTime = TIME_TO_PREPARE + currentOven.schedule(TIME_TO_PREPARE, pizzas) + directions.estimateTravelTime(); ... } } }}} そしてモジュールでは、 {{{ class PizzaModule extends AbstractModule { protected void configure() { requestStaticInjection(OrderPizzaAction.class); requestStaticInjection(PizzaUtilities.class); bind(Oven.class).toProvider(new Provider() { public Oven get() { return Oven.getCurrentOven(); } }); } } }}} こいつの意味は、Ovenが注入されるときには、いつでも古いOven.getCurrentOven()が呼び出されてオブジェクトが取得されるってことだ。 まぁ、後でこのメソッドも、とっぱらっちゃうけどね。 さてこれで、前もって特別なOvenインスタンスを準備しなくてもPizzaServicesをテストできるってわけ。 次回は、残りの静的呼び出しも片付けてしまおう。