Sunday, February 1, 2015

Disambiguating between instances with Google Guice

Google guice provides a neat way to select a target implementation if there are multiple implementations of an interface. My samples are based on an excellent article by Josh Long(@starbuxman) on a similar mechanism that Spring provides.

So, consider an interface called MarketPlace having two implementations, an AndroidMarketPlace and AppleMarketPlace:

interface MarketPlace {
}

class AppleMarketPlace implements MarketPlace {

    @Override
    public String toString() {
        return "apple";
    }
}

class GoogleMarketPlace implements MarketPlace {

    @Override
    public String toString() {
        return "android";
    }
}

and consider a user of these implementations:

class MarketPlaceUser {
    private final MarketPlace marketPlace;
    
    public MarketPlaceUser(MarketPlace marketPlace) {
        System.out.println("MarketPlaceUser constructor called..");
        this.marketPlace = marketPlace;
    }

    public String showMarketPlace() {
        return this.marketPlace.toString();
    }

}

A good way for MarketPlaceUser to disambiguate between these implementations is to use a guice feature called Binding Annotations. To make use of this feature, start by defining annotations for each of these implementations this way:

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

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

and inform the Guice binder about these annotations and the appropriate implementation corresponding to the annotation:

class MultipleInstancesModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(MarketPlace.class).annotatedWith(Ios.class).to(AppleMarketPlace.class).in(Scopes.SINGLETON);
        bind(MarketPlace.class).annotatedWith(Android.class).to(GoogleMarketPlace.class).in(Scopes.SINGLETON);
        bind(MarketPlaceUser.class).in(Scopes.SINGLETON);
    }
}

Now, if MarketPlaceUser needs to use one or the other implementation, this is how the dependency can be injected in:

import com.google.inject.*;

class MarketPlaceUser {
    private final MarketPlace marketPlace;

    @Inject
    public MarketPlaceUser(@Ios MarketPlace marketPlace) {
        this.marketPlace = marketPlace;
    }

}

This is very intuitive. If you have concerns about defining so many annotations, another approach could be to use @Named built-in Google Guice annotation, this way:

class MultipleInstancesModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(MarketPlace.class).annotatedWith(Names.named("ios")).to(AppleMarketPlace.class).in(Scopes.SINGLETON);
        bind(MarketPlace.class).annotatedWith(Names.named("android")).to(GoogleMarketPlace.class).in(Scopes.SINGLETON);
        bind(MarketPlaceUser.class).in(Scopes.SINGLETON);
    }
}

and use it this way, where the dependency is required:

import com.google.inject.*;

class MarketPlaceUser {
    private final MarketPlace marketPlace;

    @Inject
    public MarketPlaceUser(@Named("ios") MarketPlace marketPlace) {
        this.marketPlace = marketPlace;
    }

}

If you are interested in exploring this further, here is the Google guice sample and an equivalent sample using Spring framework

No comments:

Post a Comment