Dependency injection 匕首2:如何在运行时更改提供的依赖项

Dependency injection 匕首2:如何在运行时更改提供的依赖项,dependency-injection,dagger-2,Dependency Injection,Dagger 2,为了学习Dagger2,我决定重写我的应用程序,但我一直在为下面的问题寻找合适的解决方案 在本例中,我们假设有一个名为Mode的接口: public interface Mode { Object1 obj1(); //some other methods providing objects for app } 和两个实现: NormalMode和DemoMode 模式存储在singleton中,因此可以从应用程序中的任何位置访问它 public enum ModeManag

为了学习Dagger2,我决定重写我的应用程序,但我一直在为下面的问题寻找合适的解决方案

在本例中,我们假设有一个名为
Mode
的接口:

public interface Mode {
    Object1 obj1();

    //some other methods providing objects for app
}
和两个实现:
NormalMode
DemoMode

模式存储在singleton中,因此可以从应用程序中的任何位置访问它

public enum ModeManager {
  INSTANCE,;

  private Mode mode;

  public Mode mode() {
    if (mode == null)
      mode = new NormalMode();
    return mode;
  }

  public void mode(Mode mode) { //to switch modules at runtime
    this.mode = mode;
  }
}
NormalMode
在运行时切换到
DemoMode
(比方说,当用户在后台多次单击cs时)


首先,我去掉了单例,将模式定义为Dagger 2模块:

@Module
public class NormalModeModule {
  @Provides
  public Object1 provideObject1() {
    return new NormalObject1();
  }
}

@Module
public class DemoModeModule {
  @Provides
  public Object1 provideObject1() {
    return new DemoObject1();
  }
}
现在,在方法
backgroundclicked5次
中,我想用DAG中的
DemoModeModule
替换
NormalModeModule
,这样其他需要
Object1
的类从现在起将得到
DemoObject1
实现

我用匕首怎么做


提前感谢。

在对dagger进行了一段时间的实验之后,我想出了一个在我的用例中似乎运行良好的解决方案

  • 定义将保存有关模式的状态信息的类

    public class Conf {
      public Mode mode;
    
      public Conf(Mode mode) {
        this.mode = mode;
      }
    
      public enum Mode {
        NORMAL, DEMO
      }
    }
    
  • 在模块中提供
    Conf
    的单例实例

    @Module
    public class ConfModule {
      @Provides
      @Singleton
      Conf provideConf() {
        return new Conf(Conf.Mode.NORMAL);
      }
    }
    
  • 将模块添加到AppComponent

    @Singleton
    @Component(modules = {AppModule.class, ConfModule.class})
    public interface AppComponent {
        //...
    }
    
  • 定义基于模式提供不同对象的模块

    @Module
    public class Object1Module {
    
      @Provides
      Object1 provideObject1(Conf conf) {
        if (conf.mode == Conf.Mode.NORMAL)
          return new NormalObject1();
        else
          return new DemoObject1();
      }
    }
    
  • 要在运行时切换模式,只需插入
    Conf
    对象并对其进行修改:

    public class MyActivity extends Activity {
        @Inject Conf conf;
    
        //...
    
        public void backgroundClicked5Times(){
            conf.mode = Conf.Mode.DEMO;
    
            //if you have dagger objects in this class that depend on Mode
            //execute inject() once more to refresh them
        }
    }
    

  • 也许你可以考虑使用多绑定?

    @Module
    public class NormalModeModule {
      @Provides
      @IntoMap
      @StringKey("normal")
      public Object1 provideObject1() {
        return new NormalObject1();
      }
    }
    
    @Module
    public class DemoModeModule {
      @Provides
      @IntoMap
      @StringKey("demo")
      public Object1 provideObject1() {
        return new DemoObject1();
      }
    }
    
    使用模式时:

    @Inject
    Map<String, Mode> modes;
    //or you perfer lazy initialization:
    Map<String, Provider<Mode>> modes;
    
    public void backgroundClicked5Times(){
      ModeManager.INSTANCE.mode(modes.get("demo"));
      //if you are using Provider:
      ModeManager.INSTANCE.mode(modes.get("demo").get());
      //from now on every object that uses Mode will get Demo implementations, great!
    }
    
    @Inject
    地图模式;
    //或者您更喜欢延迟初始化:
    地图模式;
    公开作废背景点击5次(){
    ModeManager.INSTANCE.mode(modes.get(“demo”);
    //如果您使用的是提供商:
    ModeManager.INSTANCE.mode(modes.get(“demo”).get();
    //从现在起,每个使用模式的对象都将得到演示实现,太棒了!
    }
    
    的可能副本似乎是一个不错的方法,但当提供的依赖项(在运行时将被替换)是单例时,它就不起作用了,是吗?是的,如果您用
    @singleton
    注释
    provideObject1
    ,它就不起作用,但另一方面,它与本用例中的想法相矛盾。如果要在运行时替换对象,则不能使用
    @Singleton
    标记此类方法。另一方面,您可以将
    NormalObject1
    DemoObject1
    设置为单例,这样提供依赖项的方法不会创建超过2个实例。最后一句话就是我需要的信息。很好的解决方案,thx:)这正是我想要的。但是你能详细解释一下最后一句话吗?“如果这个类中有dagger对象依赖于模式execute inject()再次刷新它们”。我喜欢这个想法,把模块当作一个配置
    @Inject
    Map<String, Mode> modes;
    //or you perfer lazy initialization:
    Map<String, Provider<Mode>> modes;
    
    public void backgroundClicked5Times(){
      ModeManager.INSTANCE.mode(modes.get("demo"));
      //if you are using Provider:
      ModeManager.INSTANCE.mode(modes.get("demo").get());
      //from now on every object that uses Mode will get Demo implementations, great!
    }