简体   繁体   中英

How to create abstract factory for many concrete types

Let's assume we have interface named IAction , and a lot of classes (more than 20) implementing this interface: ConreteAction1 , ConreteAction2 , etc. All these classes have constructors with parameters. Also all of these classes, as well as their dependencies, are registered in Dagger module (as transient objects or singletons). Our task is to implement (or auto-generate) abstract factory implementing interface similar to:

public interface IActionFactory{
    IAction createByClass(Class clazz); // create action by type
    // or
    IAction createByName(String name); // create action by custom name
}

I know how I'm able to implement this factory using Dagger's support for Provider<T> annotation:

public class ActionFactory implements IActionFactory{

    @Inject Provider<ConcreteAction1> concreteAction1Provider;
    @Inject Provider<ConcreteAction2> concreteAction2Provider;
    (...)

    @Override
    public IAction createByClass(Class clazz){

        if(ConcreteAction1.class.equals(clazz)){
            return concreteAction1Provider.get()
        }

        if(ConcreteAction2.class.equals(clazz)){
            return concreteAction2Provider.get()
        }
        (...)
    }

    @Override
    public IAction createByName(String name){

        if(name.equals("name_1")){
            return concreteAction1Provider.get()
        }

        if(name.equals("name_2")){
            return concreteAction2Provider.get()
        }
        (...)
    }
}

Unfortunately, this approach involves a lot of boilerplate code (I have more than 20 concrete classes), and factory above has to be modified each time I create another implementation of IAction interface (violation of Open-Close principle).

Is there any other way in Dagger to create such a factory implementation in more elegant and extendable way?

You can use Multibindings for this.

@Module public interface ActionModule {
  @Binds @IntoMap @ClassKey(ConcreteAction1.class)
  IAction bindActionClass1(ConcreteAction1 action1);

  @Binds @IntoMap @ClassKey(ConcreteAction2.class)
  IAction bindActionClass2(ConcreteAction2 action2);

  // ...

  @Binds @IntoMap @StringKey("name_1")
  IAction bindActionName1(ConcreteAction1 action1);

  @Binds @IntoMap @StringKey("name_2")
  IAction bindActionName2(ConcreteAction2 action2);

  // ...
}

public class ActionFactory implements IActionFactory{
  @Inject Map<Class<?>, Provider<IAction>> classActionFactory;
  @Inject Map<String, Provider<IAction>> stringActionFactory;

  @Override
  public IAction createByClass(Class<?> clazz) {
    // TODO: handle missing entries gracefully
    return classActionFactory.get(clazz).get();
  }

  @Override
  public IAction createByName(String name) {
    return stringActionFactory.get(name).get();
  }
}

At this point the main difficulty is that you are binding each action twice, once in each map. If this is a problem, you could use a Set binding to aggregate a set of configurations and then use the map binding to retrieve the right Provider.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM