Atom Feed SITE FEED   ADD TO GOOGLE READER

Refactoring to Guice: Part 2 of N

In Part 1, I removed some of the static calls in my pizza program, leaving me with this code. It still has several static calls that interfere with testability:
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();
}
});
}
}

Injecting @Named values
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:
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());
...
}
}
And in the Module, we bind the named address using annotatedWith:
class PizzaModule extends AbstractModule {
protected void configure() {
...
bind(Address.class)
.annotatedWith(Names.named("storeAddress"))
.toInstance(PizzaStore.getStoreAddress());
}
}
Now we can test the PizzaService interface without a dependency on static methods in the PizzaStore class.

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:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER})
@BindingAnnotation
public @interface StoreAddress {}
Now we can replace the String name from the constructor with our annotation:
  @Inject
public PizzaServices(Oven currentOven,
@StoreAddress Address storeAddress) {
this.currentOven = currentOven;
this.storeAddress = storeAddress;
}
And update our module:
class PizzaModule extends AbstractModule {
protected void configure() {
...
bind(Address.class)
.annotatedWith(StoreAddress.class)
.toInstance(PizzaStore.getStoreAddress());
}
}

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

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.

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.

In a future post, I'll improve this code by replacing the GeographyServices and PizzaServices classes with interfaces.

Part 3