Locked History Actions

Diff for "guice/RefactoringToGuice/part2"

Differences between revisions 1 and 2
Deletions are marked like this. Additions are marked like this.
Line 9: Line 9:
  /** 訳注:オーブンは注入されるようになった */
Line 15: Line 16:
    /** 訳注:ここがダメ二つ */
Line 27: Line 29:
    /** 訳注:ここもダメ */
Line 28: Line 31:

    /** 訳注:ここは? */
Line 45: Line 50:
== Injecting @Named values == == @Named付の値を注入する ==
Line 47: Line 52:
Today I'll use injection and annotations to replace the static call to PizzaStore.getStoreAddress(). We could just bind the store address in the injector, but that could be confusing if there are multiple addresses in the program. The fix is to name this address with a binding annotation: さて今日は、PizzaStore.getStoreAddress()への静的呼び出しをつぶすために、注入とアノテーションを使ってみよう。
単純に、お店の住所(store address)をインジェクタにバインドすることもできるんだけど、
でもそんなことをするとプログラムの中にいろんな住所がある場合に混乱するかもしれない。
ならば、住所をバインディングアノテーションで名前付けしてあげればいいと思うよ。
Line 67: Line 75:
And in the Module, we bind the named address using annotatedWith: そして、モジュール内では名前付けされた住所についてannotatedWithを使い、
Line 78: Line 86:
Now we can test the PizzaService interface without a dependency on static methods in the PizzaStore class. さてこれで、PizzaStoreクラスの静的メソッドに依存することなく、PizzaServiceインターフェースをテストできるってもんだ。
Line 80: Line 88:
== Removing Strings with custom annotations ==
If the use of the String "storeAddress" upsets you, we can replace that name with a custom annotation. In the file StoreAddress.java, we create the annotation, which itself must be heavily annotated:
== カスタムアノテーションを使って文字列を省略する ==

もしかして、"storeAddress"なんて文字列に我慢がならなかったりする?
カスタムアノテーションを使えば、これを置き換えることができるんだ。
StoreAddress.javaというファイルを作り、そこにアノテーションを書く、
そのアノテーション自体が結構アノテーションされるわけなんだけど。以下のように、
Line 88: Line 100:
Now we can replace the String name from the constructor with our annotation: さて、コンストラクタの文字列名称を削除して、このアノテーションを使ってみよう。
Line 97: Line 109:
And update our module: モジュールも変更しとこうね。
Line 108: Line 120:

Replacing a static method with an instance method
Our PizzaServices class still has a big dependency on Geography.java for its getDirections method. Fortunately, we already know how to do this - just as we swapped PizzaUtilities with PizzaServices in Part 1, we can replace Geography with GeographyServices here. Then we can inject that into our PizzaServices class:
静的メソッドをインスタンスメソッドで置き換えつつあるけど、
われらがPizzaServicesクラスは、Geograpy.javaに大きく依存している、getDirectionsメソッドを使うためにね。
ラッキーだな、もうどうすればよいかわかるよね。
パート1でPizzaUtilitiesとPizzaServicesを交換したのと同じように、
Geograpyと(新たな)GeograpyServiceを置き換えればいいんだよね。
そんでもって、こいつをPizzaServicesに注入すればいいってわけ。
Line 134: Line 149:
With the changes so far, we can create custom subclasses of Oven and GeographyServices to test createOrder without dependencies. This means that our test will run faster and provide no false negatives. これまでの変更で、createOrderを依存なくテストするのに、OvenとGeograpyServicesのサブクラスを作成できるようになった。
これは、我々のテストが高速に動き、false negativesが無いってことだよ。
Line 136: Line 152:
The biggest benefit I get from non-static, injectable code is that if I need to make changes to the implementation of PizzaServices, the edit-compile-execute cycle is dramatically faster. 非静的な注入可能なコードの最もでかいメリットというのは、PizzaServicesの実装を変更したいと思ったときに、edit-compile-executeサイクルがドラマチックに素早くなるということなんだ。
Line 138: Line 154:
In a future post, I'll improve this code by replacing the GeographyServices and PizzaServices classes with interfaces. 次回は、GeographyServicesとPizzaServicesクラスをインターフェースに置き換えてコード改良をしてみよう。

パート2

パート1で、ピザプログラムのいくつかの静的呼び出しを片付けた。 でもまだまだテストの邪魔をする静的呼び出しが残ってるぞ。

public class PizzaServices {
  private final Oven currentOven;

  /** 訳注:オーブンは注入されるようになった */
  @Inject
  public PizzaServices(Oven currentOven) {
    this.currentOven = currentOven;
  }

  public 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
        + currentOven.schedule(TIME_TO_PREPARE, pizzas)
        + directions.estimateTravelTime();

    /** 訳注:ここもダメ */
    Invoice invoice = Invoice.create(pizzas, directions.getLengthInKm());

    /** 訳注:ここは? */
    return new Order(pizzas, invoice, arrivalTime, customer, directions);
  }
}

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();
      }
    });
  }
}

@Named付の値を注入する

さて今日は、PizzaStore.getStoreAddress()への静的呼び出しをつぶすために、注入とアノテーションを使ってみよう。 単純に、お店の住所(store address)をインジェクタにバインドすることもできるんだけど、 でもそんなことをするとプログラムの中にいろんな住所がある場合に混乱するかもしれない。 ならば、住所をバインディングアノテーションで名前付けしてあげればいいと思うよ。

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

  @Inject
  public PizzaServices(Oven currentOven,
      @Named("storeAddress") Address storeAddress) {
    this.currentOven = currentOven;
    this.storeAddress = storeAddress;
  }

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

そして、モジュール内では名前付けされた住所についてannotatedWithを使い、

class PizzaModule extends AbstractModule {
  protected void configure() {
    ...
    bind(Address.class)
        .annotatedWith(Names.named("storeAddress"))
        .toInstance(PizzaStore.getStoreAddress());
  }
}

さてこれで、PizzaStoreクラスの静的メソッドに依存することなく、PizzaServiceインターフェースをテストできるってもんだ。

カスタムアノテーションを使って文字列を省略する

もしかして、"storeAddress"なんて文字列に我慢がならなかったりする? カスタムアノテーションを使えば、これを置き換えることができるんだ。 StoreAddress.javaというファイルを作り、そこにアノテーションを書く、 そのアノテーション自体が結構アノテーションされるわけなんだけど。以下のように、

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER})
@BindingAnnotation
public @interface StoreAddress {}

さて、コンストラクタの文字列名称を削除して、このアノテーションを使ってみよう。

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

モジュールも変更しとこうね。

class PizzaModule extends AbstractModule {
  protected void configure() {
    ...
    bind(Address.class)
        .annotatedWith(StoreAddress.class)
        .toInstance(PizzaStore.getStoreAddress());
  }
}

静的メソッドをインスタンスメソッドで置き換えつつあるけど、 われらがPizzaServicesクラスは、Geograpy.javaに大きく依存している、getDirectionsメソッドを使うためにね。 ラッキーだな、もうどうすればよいかわかるよね。 パート1でPizzaUtilitiesPizzaServicesを交換したのと同じように、 Geograpyと(新たな)GeograpyServiceを置き換えればいいんだよね。 そんでもって、こいつをPizzaServicesに注入すればいいってわけ。

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());
    ...
  }
}

これまでの変更で、createOrderを依存なくテストするのに、OvenとGeograpyServicesのサブクラスを作成できるようになった。 これは、我々のテストが高速に動き、false negativesが無いってことだよ。

非静的な注入可能なコードの最もでかいメリットというのは、PizzaServicesの実装を変更したいと思ったときに、edit-compile-executeサイクルがドラマチックに素早くなるということなんだ。

次回は、GeographyServicesPizzaServicesクラスをインターフェースに置き換えてコード改良をしてみよう。