Take a look on that pseudocode
class A,B,C implements StrategyInterface
{
private dep;
constructor(Dep dep) {
this->dep = dep;
}
}
class StrategyResolver
{
private locator;
constructor(ServiceLocator locator) {
this->locator = locator;
}
public function resolve(data): StrategyInterface
{
if ( xxx ) {
return locator->get(A);
} else if ( yyy ) {
return locator->get(B);
}
return locator->get(C);
}
}
As service locator is considered as anti pattern, how to avoid it in this case? A,B,C
can have various dependencies, thats why I would like to instantiate it using all benefits of dependency injections. I could albo inject A,B,C
as dependencies of StrategyResolver
, but what if I have there like 10 strategies. The StrategyResolver
dependency list is then too long.
If you are using Spring you can inject all known beans of a certain type automatically:
private final List<StrategyInterface> strategies;
public StrategyResolver(List<StrategyInterface> strategies) {
this.strategies = strategies;
}
If this is the only constructor Spring will have no trouble injecting all the beans implementing the StrategyInterface. Of course those beans need to be known to Spring using for example the @Named annotation on the class:
@javax.inject.Named
public class A implements StrategyInterface {
BTW why not just name the interface 'Strategy'? The 'Interface' suffix does not really add anything.
EDIT: to determine which Strategy applies you can add a method to the Strategy interface:
@javax.inject.Named
public class A implements StrategyInterface {
@Override
public boolean appliesTo(String data) {
// just an example
return "xxx".equals(data);
}
In the StrategyResolver you would have code like this:
public StrategyInterface resolve(String data) {
for(StrategyInterface strategy : strategies) {
if (strategy.appliesTo(data)) {
return strategy;
}
}
throw new IllegalArgumentException("No strategy applies to " + data);
}
Or in more recent style:
public StrategyInterface resolve(String data) {
return strategies.stream().filter(s -> s.appliesTo(data)).findAny()
.orElseThrow(() -> new IllegalArgumentException("No strategy applies to " + data));
}
One gotcha here is that to avoid the exception above you must ensure that a strategy always applies. You could create a 'DefaultStrategy' which always applies and is added as the last strategy in the list, e.g:
private final List<StrategyInterface> strategies;
public StrategyResolver(List<StrategyInterface> strategies, DefaultStrategy defaultStrategy) {
this.strategies = new ArrayList<>(strategies);
this.strategies.add(defaultStrategy);
}