2010-12-10 42 views
4

Je voudrais instancier plusieurs versions du même type d'arbre/chaîne de dépendances qui utilisent différentes implémentations pour certaines des interfaces de cette arborescence/chaîne. Quelle est la meilleure pratique/modèle de Guice à utiliser dans un tel cas?Quel est le meilleur modèle pour gérer plusieurs versions de la même arborescence de dépendances dans Guice?

Voici un exemple concret de mon problème.

J'ai une interface Writer qui peut potentiellement être un écrivain de fichiers ou un écrivain std-out, qui sera situé à la feuille de ma hiérarchie de dépendance. Quelque chose comme ceci:

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

Une autre interface de l'enregistreur est utilisée pour ajouter une couche d'indirection sur l'enregistreur. Par exemple:

interface Logger { ... } 

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

Ensuite, il y a un client qui utilise l'enregistreur.

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

Maintenant, je voudrais utiliser deux types de hiérarchies dans mon programme:

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

Existe-t-il une bonne façon de câbler ceci sans utiliser un module Guice séparé pour 1 et 2?

Idéalement je voudrais avoir une classe ClientFactory comme ceci:

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

Quelqu'un pourrait-il trouver un moyen de câbler cela en utilisant cette usine, ou par tout autre moyen?

Je voudrais également une solution qui serait mise à l'échelle dans les cas où j'ai une plus grande variété d'arbres/chaînes plus dépendants. Merci!

Répondre

6

Ceci est le problème robot legs. La solution consiste essentiellement à utiliser PrivateModule s pour lier chaque arbre de dépendance et exposer uniquement la racine de cet arbre. Il y a plusieurs façons dont vous pouvez le faire, mais voici un exemple de la façon dont vous pouvez généralement le faire (il y a beaucoup de variations sur ce que vous pouvez faire en fonction de vos besoins):

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

    ... 
} 

Votre scénario d'une méthode Client fileClient(File) est un peu différent cependant.

+0

Merci pour une excellente explication! J'ai enfin compris les modules privés et le problème des jambes du robot. Cela résout la question pour moi. Mais la raison pour laquelle je voulais utiliser la variable fileClient (File) est que je n'utilise pas du tout la variante @FileOut du Client au moment de l'exécution (disons, cela dépend d'un argument de ligne de commande). Je pourrais instancier un module de guice différent selon qu'il y a un fichier non fourni, mais ce n'est pas très élégant, et au fur et à mesure que le nombre de mes variations d'arborescence de dépendances augmenterait, cela deviendrait plutôt désagréable. – rodion