Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best pattern for managing multiple versions of the same dependency tree in Guice?

I would like to instanciate several versions of the same kind of dependency tree/chain which use different implementations for some of the interfaces in that tree/chain. What is the best Guice practice/pattern to use in such case?

Here is a concrete example of my problem.

I have a Writer interface which can potentially be a file writer or a std-out writer, which will be situated at the leaf of my dependency hierarchy. Something like this:

interface Writer { ... }
class FileWriter implements Writer { ... }
class StdOutWriter implements Writer { ... }

Another logger interface is used to add a layer of indirection on the writer. For instance:

interface Logger { ... }

class LoggerImpl{
  @Inject
  public Logger(Writer out){ ... }
  public void log(String message){ out.println(message); }
}

Then there is a client that uses the logger.

class Client{
  @Inject
  public Client(Logger logger){ ... }
  public void do(){ logger.log("my message"); }
}

Now, I would like to use two types of hierarchies in my program:

  1. Client -> LoggerImpl -> FileWriter
  2. Client -> LoggerImpl -> StdOutWriter

Is there a nice way of wiring this up without using a separate Guice module for 1 and 2?

Ideally I would like to have a ClientFactory class like this:

interface ClientFactory{
  public Client stdOutClient();
  public Client fileClient(); 
  //or fileClient(File outputFile) for extra points ;)
}

Could anyone come up with a way to wire up this using this factory, or by any other means?

I would also like a solution that would scale to cases where I have a bigger variety of longer dependency trees/chains. Thanks!

like image 857
rodion Avatar asked Dec 10 '10 17:12

rodion


1 Answers

This is the robot legs problem. The solution is basically to use PrivateModules to bind each dependency tree and expose only the root of that tree. There are several ways you could do this, but here's an example of how you typically might do it (there are a lot of variations on this you can do according to your needs):

public class ClientModule extends PrivateModule {
  private final Writer writer;
  private final Class<? extends Annotation> annotationType;

  public ClientModule(Writer writer, Class<? extends Annotation> annotationType) {
    this.writer = writer;
    this.annotationType = annotationType;
  }

  @Override protected void configure() {
    bind(Writer.class).toInstance(writer);
    bind(Logger.class).to(LoggerImpl.class);
    expose(Client.class).annotatedWith(annotationType);
  }
}

public class ClientFactoryModule extends AbstractModule {
  private final File file;

  public ClientFactoryModule(File file) {
    this.file = file;
  }

  @Override protected void configure() {
    install(new ClientModule(new StdOutWriter(), StdOut.class));
    install(new ClientModule(new FileWriter(file), FileOut.class));
    bind(ClientFactory.class).to(ClientFactoryImpl.class);
  }
}

public class ClientFactoryImpl implements ClientFactory {
  private final Client stdOutClient;
  private final Client fileClient;

  @Inject public ClientFactoryImpl(@StdOut Client stdOutClient, 
                                   @FileOut Client fileClient) {
    this.stdOutClient = stdOutClient;
    this.fileClient = fileClient;
  }

  ...
}

Your scenario of a method Client fileClient(File) is quite a bit different though.

like image 103
ColinD Avatar answered Sep 19 '22 04:09

ColinD