Locked History Actions

Diff for "guice/RefactoringToGuice/part1"

Differences between revisions 3 and 4
Deletions are marked like this. Additions are marked like this.
Line 147: Line 147:

Now that PizzaUtilities is injected, we can start to inject its dependencies into it. The low-hanging fruit is the Oven.getCurrentOven() singleton. We'll bind that in the module and then we can inject it!
さて、PizzaUtilitiesに注入することができた、今度はそいつ(PizzaServices)に注入することにしようか。
手近なものはOven.getCurrentOven()シングルトンだ。
こいつを注入できるようにするためには、モジュールでバインド指定する。
Line 167: Line 168:
and then in the Module: そしてモジュールでは、
Line 181: Line 182:
This means that whenever an Oven instance is injected, it'll use the old Oven.getCurrentOven() method to get it. Later on, we'll be able to remove that method as well. こいつの意味は、Oevnが注入されるときには、いつでも古いOven.getCurrentOven()が呼び出されてオブジェクトが取得されるってことだ。
まぁ、後でこのメソッドも、とっぱらっちゃうけどね。
Line 183: Line 185:
Now we can test PizzaServices without a particular Oven instance prepared in advance. In a future post, I'll demonstrate how to remove the remaining static calls. さてこれで、前もって特別なOvenインスタンスを準備しなくてもPizzaServicesをテストできるってわけ。
次回は、残りの静的呼び出しも片付けてしまおう。

Guiceへのリファクタリング

ヒドイコード

こういうのどこにでもあるでしょ!静的なユーティリティメソッドがいろんなものに依存していて、それもまた静的ときたもんだ。

public class PizzaUtilities {
  private static final int TIME_TO_PREPARE = 6;
  private static final int MAX_DISTANCE = 20;

  public static Order createOrder(List<PizzaSpec> 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<PizzaSpec> 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<PizzaSpec> 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<PizzaSpec> pizzas, Customer customer) {
    Directions directions = Geography.getDirections(
      PizzaStore.getStoreAddress(), customer.getDeliveryAddress());
    ...
    return new Order(pizzas, invoice, arrivalTime, customer, directions);
  }
}

PizzaUtilitiesPizzaServicesを注入するため、これまた静的注入を指定することにしよう。

class PizzaModule extends AbstractModule {
  protected void configure() {
    requestStaticInjection(OrderPizzaAction.class);
    requestStaticInjection(PizzaUtilities.class);
  }
}

Injecting the non-static version

さて、PizzaUtilitiesに注入することができた、今度はそいつ(PizzaServices)に注入することにしようか。 手近なものはOven.getCurrentOven()シングルトンだ。 こいつを注入できるようにするためには、モジュールでバインド指定する。

public class PizzaServices {
  private final Oven currentOven;

  @Inject
  public PizzaServices(Oven currentOven) {
    this.currentOven = currentOven;
  }

  public Order createOrder(List<PizzaSpec> 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();
      }
    });
  }
}

こいつの意味は、Oevnが注入されるときには、いつでも古いOven.getCurrentOven()が呼び出されてオブジェクトが取得されるってことだ。 まぁ、後でこのメソッドも、とっぱらっちゃうけどね。

さてこれで、前もって特別なOvenインスタンスを準備しなくてもPizzaServicesをテストできるってわけ。 次回は、残りの静的呼び出しも片付けてしまおう。