Dagger 2模块“接口”?

时间:2017-04-21 19:57:51

标签: java interface inversion-of-control dagger-2

我对Dagger 2很陌生,我正在寻找一种“可配置组件”的方法。

基本上这就是我想要实现的目标:

public interface ErrorReporter{
   ...
}

public class ConsoleErrorReporter implements ErrorReporter{
   ... // Print to System.err
}

public class DialogErrorReporter implements ErrorReporter{
   ... // Show modal dialog to user
}


@Module
public interface UIModule{
    @Provides
    ErrorReporter provideErrorReporter();
}

@Module
public class ConsoleUIModule{
    @Override
    @Provides
    ErrorReporter provideErrorReporter(ConsoleErrorReporter cer){
        return cer;
    }
}


@Module
public class GraphicalUIModule{
    @Override
    @Provides
    ErrorReporter provideErrorReporter(DialogErrorReporter  der){
        return der;
    }
}

@Component(modules = {UIModule.class, OtherUniversalModule.class})
public interface ApplicationComponent{
    ErrorReporter errorReporter();
}


void main(String[] args){
    final UIModule uiModule;
    if(args.length == 1 && args[0].equals("gui")){
        uiModule = new GraphicalUIModule();
    }else{
        uiModule = new ConsoleUIModule();
    }

    DaggerApplicationComponentdac = DaggerApplicationComponent.builder()
                                       .uiModule(uiModule).build();
    dac.errorReporter().showError("Hello world!");
}

不幸的是,对于接口和抽象类,上面的@Provides methods cannot be abstract失败了。我还尝试了非抽象基类,其具体实现返回null,然后在子类中覆盖它们。但是,这也会因@Provides methods may not override another method而失败。

简而言之,我想为模块定义一个契约,并在运行时选择不同的模块。我知道Dagger 2编译时验证了对象图,但是如果我有一个明确定义的合同应该仍然可能吗?或者我是否被迫为两个用户界面创建两个具有重复代码的不同组件?我还缺少其他解决方案吗?

1 个答案:

答案 0 :(得分:4)

我不认为以这种方式使用模块是可能的,因为......

假设您的类具有以下两个构造函数

@Inject ConsoleErrorReporter(Console console);

@Inject DialogErrorReporter(Graphics graphics);

这意味着ConsoleUIModule需要ConsoleDialogErrorReporter需要Graphics个对象来创建ErrorReporter的重新实现。

但是如果匕首只知道UIModule,因为你在那里使用界面......那么......它无法提供任何依赖关系,因为它不知道它们中的任何一个。 / p>

如果您不了解依赖关系,那么在编译时构建依赖关系图将无法正常工作。即使没有匕首也不会编译,因为provideErrorReporter(ConsoleErrorReporter cer)不会覆盖provideErrorReporter()

您可以而且应该做的是使用不同的组件。因为组件实际上知道如何提供东西。一个组件已经是一个界面 - 这就是你想要的,对吧?

您可以拥有组件依赖关系,其中一个组件依赖于另一个组件。例如。有一个DependentComponent提供NeedsErrorReporter,需要ErrorReporter的实施。我们还依赖于一个界面,而不是实际的组件(毕竟这是你想要的,对吗?)

然后,您可以通过实际组件实现接口,并且每个组件都有其各自的模块(甚至可能还有其他依赖项)。最后,你有一个可以切换的组件,它将提供一个正确封装的对象的不同版本!

@Component(dependencies = UIComponent.class) /* <- an interface! */
interface DependentComponent {
  NeedsErrorReporter needsErrorReporter();
}

class NeedsErrorReporter {
  @Inject public NeedsErrorReporter(ErrorReporter reporter) { }
}


/* this is _not_ a component, but a simple interface! */
interface UIComponent {
  ErrorReporter errorReporter();
}


/* Console */
@Component(modules = ConsoleUIModule.class)
interface ConsoleUIComponent extends UIComponent { }

@Module interface ConsoleUIModule {
  @Binds ErrorReporter provideErrorReporter(ConsoleErrorReporter cer);
}

/* Graphic */
@Component(modules = GraphicalUIModule.class)
interface GraphicUIComponent extends UIComponent { }

@Module interface GraphicalUIModule {
  @Binds ErrorReporter provideErrorReporter(DialogErrorReporter der);
}

/* The error reporter variants */
interface ErrorReporter {
}

class ConsoleErrorReporter implements ErrorReporter {
  @Inject public ConsoleErrorReporter() { }
}

class DialogErrorReporter implements ErrorReporter {
  @Inject public DialogErrorReporter() { }
}

现在你所要做的就是选择正确的组件;)

DaggerDependentComponent.builder().uIComponent(DaggerConsoleUIComponent.create()).build();
    // or
DaggerDependentComponent.builder().uIComponent(DaggerGraphicUIComponent.create()).build();
相关问题