Locked History Actions

Diff for "guice/RefactoringToGuice/part3"

Differences between revisions 3 and 4
Deletions are marked like this. Additions are marked like this.
Line 123: Line 123:
== Looking forward: Simplifying the factory's implementation == == ファクトリの実装をシンプルにする ==
Line 125: Line 125:
I've had to write a lot of code to implement the Invoice.Factory anonymous inner class. This is the best design, but it's more complex than the static method call. Better code should be easier to write otherwise I usually get lazy and revert to the static factory. Invoice.Factoryという匿名内部クラスを実装するためにたくさんのコードを書いた。
これがベストのデザインなのだけど、ただ静的メソッド呼び出しよりだいぶ複雑になっちゃったな。
よいコードは簡単に書けないといけない、さもないと嫌になって静的ファクトリに戻りたくなるからね。
Line 127: Line 129:
In the next release of Guice, user-defined factory interfaces will be supported. We first annotate the Invoice constructor's injected parameter: 次のGuiceでは(訳注:2.0のこと、既にリリース済み)、ユーザ定義ファクトリインターフェースがサポートされる。
まず、Invoiceのコンストラクタの注入パラメータをアノテートする。
Line 136: Line 139:
and then we can bind the factory directly to the class it constructs: そして、対象のファクトリを直接的に作成すべきクラスにバインドする。
Line 145: Line 148:
This is equivalent to our factory inner class, but much more concise. If we change the signature of the Invoice constructor to add or remove injectable paramters, we don't need to update the factory interface. こいつは、さっきのファクトリ内部クラスと等価な働きなんだけど、でもかなり簡単になったでしょ?
もし、Invoiceのコンストラクタの注入パラメータを追加・削除してもファクトリインターフェースを変更する必要はないんだ。
Line 147: Line 151:
Guice uses Java's dynamic proxy mechanism to implement the factory interface at runtime. It aggregates the factory method's parameters with injected parameters from the Injector to build the instance at create time. GuiceはJavaの動的プロキシメカニズムを使って実行時にファクトリインターフェースを実装する。
インスタンス生成時には、ファクトリメソッドのパラメータと注入されるパラメータを一緒にするんだ
(訳注:そしてコンストラクタ引数として引き渡す)。
Line 149: Line 155:
If you can't wait for Guice 2.0, there's a similar API called AssistedInject that works with Guice 1.1. もしGuice2.0が待てないのであれば、Guice1.1では同じようなAPIであるAssistedInjectというのが使えるよ。

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

パート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);
  }
}

Now I can test the PizzaServices class without first preparing the static call to PizzaStore.getStoreAddress(). The code makes no static method calls and Guice does the wiring.

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

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しようぜ!