Locked History Actions

guice/RefactoringToGuice/part3

パート3

パート1,2で、ピザプログラムからいくつかの静的呼び出しを取り除いた。 今やサービスクラスはオブジェクト指向デザインの恩恵を受けられるようになった。 静的メソッドを非静的メソッドに置き換えればポリモフィズムを利用できるんで、テストが楽になるんだ。

public class PizzaServices {
  private final Oven currentOven;
  private final Address storeAddress;
  private final GeographyServices geographyServices;

  @Inject
  public PizzaServices(Oven currentOven,
      @StoreAddress Address storeAddress,
      GeographyServices geographyServices) {
    this.currentOven = currentOven;
    this.storeAddress = storeAddress;
    this.geographyServices = geographyServices;
  }

  public Order createOrder(List<PizzaSpec> pizzas, Customer customer) {
    Directions directions = geographyServices.getDirections(
      storeAddress, customer.getDeliveryAddress());

    if (directions == null || directions.getLengthInKm() > MAX_DISTANCE) {
      throw new InvalidOrderException("Cannot deliver to , " +
          customer.getDeliveryAddress());
    }

    int arrivalTime = TIME_TO_PREPARE
        + currentOven.schedule(TIME_TO_PREPARE, pizzas)
        + directions.estimateTravelTime();

    Invoice invoice = Invoice.create(pizzas, directions.getLengthInKm());
    return new Order(pizzas, invoice, arrivalTime, customer, directions);
  }
}

ポリモフィズムと静的ファクトリメソッド

残念だな、このコードにはまだ静的メソッドが残ってる。 ファクトリメソッドのInvoice.create()がそれだ。 実装を見てみると、このメソッドの中でまたまた静的にお店の住所を調べるなんてことをしてやがる。

class Invoice {
  public Invoice(List<PizzaSpec> pizzas, int deliveryDistance,
      Address storeAddress) {
  }
  static Invoice create(List<PizzaSpec> pizzas, int deliveryDistance) {
    return new Invoice(pizzas, deliveryDistance, PizzaStore.getStoreAddress());
  }
}

パート2でお店のアドレスを注入可能にしたわけなんだけど、それがここにも必要ってわけ。

まず、Invoice.javaの中にファクトリインターフェースを作っておこう。

class Invoice {
  ...
  interface Factory {
    Invoice create(List<PizzaSpec> pizzas, int deliveryDistance);
  }
}

このインターフェースをモジュールの中で匿名クラスとして実装する。

static class PizzaModule extends AbstractModule {
  protected void configure() {
    ...
    bind(Invoice.Factory.class).toInstance(new Invoice.Factory() {
      @Inject @StoreAddress Provider<Address> storeAddressProvider;
      public Invoice create(List<PizzaSpec> pizzas, int deliveryDistance) {
        return new Invoice(pizzas, deliveryDistance, storeAddressProvider.get());
      }
    });
  }
}

ファクトリの実装について

toInstance()でバインドされたものは、インジェクタが作成されたときに注入される。 そのため、ファクトリやプロバイダは他のモジュールで提供されるサービスに依存することができるんだ。 コンストラクタ注入が一般的には推奨されるけど、このケースの場合はフィールド注入でも十分だよ。

ファクトリの実装では、住所を直接にではなく、Provider<Address>をバインドするようにしたけど、 この住所は定数なので必ずしもこうしなくてもいい。 でも、ファクトリの中ではいつもプロバイダを使うようにすべきだね。 そうすれば、たとえそれが何らかのスコープやコンテキストに依存するときでも、いつでも正しいインスタンスを取得できるから。 僕はファクトリやプロバイダの実装中ではいつもプロバイダを使うようにしている(訳注:?)。

注入されたファクトリを使う

さて、Invoice.create()メソッドという静的呼び出しをファクトリに置き換えるために、そいつを注入してみよう。

public class PizzaServices {
  private final Oven currentOven;
  private final Address storeAddress;
  private final GeographyServices geographyServices;
  private final Invoice.Factory invoiceFactory;

  @Inject
  public PizzaServices(Oven currentOven,
      @StoreAddress Address storeAddress,
      GeographyServices geographyServices,
      Invoice.Factory invoiceFactory) {
    this.currentOven = currentOven;
    this.storeAddress = storeAddress;
    this.geographyServices = geographyServices;
    this.invoiceFactory = invoiceFactory;
  }

  public Order createOrder(List<PizzaSpec> pizzas, Customer customer) {
    ...
    Invoice invoice = invoiceFactory.create(pizzas, directions.getLengthInKm());
    return new Order(pizzas, invoice, arrivalTime, customer, directions);
  }
}

これで最初にPizzaStore.getStoreAddress()なんていう静的メソッドを用意しなくても、PizzaServicesクラスをテストできるようになった。 このコードはもはや静的メソッドを使っていないし、Guiceがワイヤリングしてくれるんだ。

ファクトリの実装をシンプルにする

Invoice.Factoryという匿名内部クラスを実装するためにたくさんのコードを書いた。 これがベストのデザインなのだけど、ただ静的メソッド呼び出しよりだいぶ複雑になっちゃったな。 よいコードは簡単に書けないといけない、さもないと嫌になって静的ファクトリに戻りたくなるからね。

次のGuiceでは(訳注:2.0のこと、既にリリース済み)、ユーザ定義ファクトリインターフェースがサポートされる。 まず、Invoiceのコンストラクタの注入パラメータをアノテートする。

static class Invoice {
  public Invoice(List<PizzaSpec> pizzas, int deliveryDistance,
      @Inject @StoreAddress Address storeAddress) {
  }
  ...
}

そして、対象のファクトリを直接的に作成すべきクラスにバインドする。

static class PizzaModule extends AbstractModule {
  protected void configure() {
    ...
    bind(Invoice.Factory.class).toFactoryFor(Invoice.class);
  }
}

こいつは、さっきのファクトリ内部クラスと等価な働きなんだけど、でもかなり簡単になったでしょ? もし、Invoiceのコンストラクタの注入パラメータを追加・削除してもファクトリインターフェースを変更する必要はないんだ。

GuiceはJavaの動的プロキシメカニズムを使って実行時にファクトリインターフェースを実装する。 インスタンス生成時には、ファクトリメソッドのパラメータと注入されるパラメータを一緒にするんだ (訳注:そしてコンストラクタ引数として引き渡す)。

もしGuice2.0が待てないのであれば、Guice1.1では同じようなAPIであるAssistedInjectというのが使えるよ。

訳注:実際の2.0リリースでは仕様がかなり異なっている。ProviderとFactoryProviderを参照のこと。

このシリーズのまとめ

さあて、静的呼び出しを全部退治して、PizzaServicesクラスは完全にポリモフィックになり、テスト可能になった。 このシリーズでは、定数注入、アノテーション、ファクトリ注入をデモした。

静的でグローバルなコードをテスト可能なポリモフィックなコードに変えてしまうことが、Guiceの核となる能力なんだ。 インターフェースと実装を分離可能にし、依存を自動的にワイヤリングしてくれる。 オブジェクト指向デザインを享受したいのなら、君のアプリもguicifyしようぜ!