Reputation: 475
Hi I have a strategy pattern in a spring boot application. All my strategies have autowired constructors. I am new to spring boot. I do not have a simplest of idea how am I going to write my factory for strategy classes as autowired constructors have injected dependencies. I appreciate any help I get with this.
NOTE: I am leaving out out Intefaces and base classes to not to clutter sample.
public class StrategyA implement Strategy {
private DependencyA depA;
private DependencyB depB;
@Autowired
public StragegyA(DependencyA depA, DependencyB depB) {
this.depA = depA;
this.depB = depB;
}
}
public class StrategyB implements Strategy {
private DependencyA depA;
private DependencyB depB;
@Autowired
public StragegyB(DependencyA depA, DependencyB depB) {
this.depA = depA;
this.depB = depB;
}
}
public class StrategyFactory {
public Strategy getStrategy(String strategyName) {
if (name.equals("StrategyA")) {
<b>return StrategyA; //My problem is here
} else {
return StrategyB; // And Here
}
}
}
Upvotes: 8
Views: 8490
Reputation: 9623
StrategyFactory
is another Bean which holds all the implementations of Strategy
@Component
public class StrategyFactory {
private Map<StrategyName, Strategy> strategies;
@Autowired
public StrategyFactory(Set<Strategy> strategySet) {
createStrategy(strategySet);
}
public Strategy findStrategy(StrategyName strategyName) {
return strategies.get(strategyName);
}
private void createStrategy(Set<Strategy> strategySet) {
strategies = new HashMap<StrategyName, Strategy>();
strategySet.forEach( strategy -> strategies.put( strategy.getStrategyName(), strategy));
}
}
Strategy
interface and all its implementations
public interface Strategy {
void doStuff();
StrategyName getStrategyName();
}
@Component
public class StrategyA implements Strategy{
@Override
public void doStuff() {
}
@Override
public StrategyName getStrategyName() {
return StrategyName.StrategyA;
}
}
@Component
public class StrategyB implements Strategy{
@Override
public void doStuff() {
}
@Override
public StrategyName getStrategyName() {
return StrategyName.StrategyB;
}
}
@Component
public class StrategyC implements Strategy{
@Override
public void doStuff() {
}
@Override
public StrategyName getStrategyName() {
return StrategyName.StrategyC;
}
}
We have Enum for all Strategies names
public enum StrategyName {
StrategyA,
StrategyB,
StrategyC
}
Finally you can wire the StrategyFactory
@Autowired
private StrategyFactory strategyFactory;
and get the required strategy
strategyFactory.findStrategy(StrategyName.StrategyA);
Upvotes: 5
Reputation: 7123
All the previous answers are using a pretty straight forward usage of spring DI. However it's also possible to use ServiceLocatorFactoryBean in order to create a factory without having to specify any bean in the factory. First define a interface for your factory:
public interface MyFactory {
Strategy get(String type);
}
// Could be an abstract class
public interface Strategy {
void doStuff();
}
Then in your application:
@Configuration
public class AppConfiguration {
@Autowired
private BeanFactory beanFactory;
public ServiceLocatorFactoryBean myFactoryLocator() {
final ServiceLocatorFactoryBean locator = new ServiceLocatorFactoryBean();
locator.setServiceLocatorInterface(MyFactory.class);
locator.setBeanFactory(beanFactory);
return locator;
}
@Bean
public MyFactory myFactory() {
final ServiceLocatorFactoryBean locator = myFactoryLocator();
locator.afterPropertiesSet();
return (MyFactory) locator.getObject();
}
}
Now you can define bean (using annotation @Service, @Component or @Bean) that implements/extends and they are automatically registered into the MyFactory bean and can be created with:
myFactory.get("beanName");
The best part is you can register the Strategy bean as lazy and with different scopes.
Upvotes: 5
Reputation: 3561
I would suggest you to make your StrategyFactory
a bean and inject into it a Map<String, Strategy>
. Spring fill it with the name of the strategy bean as a key and a value will be a strategy itself. Then all you'll need to do is to call get
on that Map
.
Here is an example:
@SpringBootApplication
public class So44761709Application {
public static void main(String[] args) {
SpringApplication.run(So44761709Application.class, args);
}
public interface Strategy { }
@Component
public static class DependencyA {}
@Component
public static class DependencyB {}
@Component("StrategyA")
public static class StrategyA implements Strategy {
private DependencyA depA;
private DependencyB depB;
@Autowired
public StrategyA(DependencyA depA, DependencyB depB) {
this.depA = depA;
this.depB = depB;
}
}
@Component("StrategyB")
public class StrategyB implements Strategy {
private DependencyA depA;
private DependencyB depB;
@Autowired
public StrategyB(DependencyA depA, DependencyB depB) {
this.depA = depA;
this.depB = depB;
}
}
@Component
public class StrategyFactory {
@Autowired
private Map<String, Strategy> strategies;
public Strategy getStrategy(String strategyName) {
return strategies.get(strategyName);
}
}
@Bean
CommandLineRunner run(StrategyFactory strategyFactory) {
return args -> {
System.out.println(strategyFactory.getStrategy("StrategyB").getClass().getSimpleName());
System.out.println(strategyFactory.getStrategy("StrategyA").getClass().getSimpleName());
};
}
}
Prints:
StrategyB
StrategyA
Upvotes: 3
Reputation: 57381
@Component
public class StrategyFactory {
private StrategyA sA;
private StrategyB sB;
@Autowired
public StrategyFactory (StrategyA sA, StrategyB sB) {
this.sA = sA;
this.sB = sB;
}
public Strategy getStrategy(String strategyName) {
if (name.equals("StrategyA")) {
return sA; //My problem is here
} else {
return sB; // And Here
}
}
}
Use the same approach with autowiring all the strategies
Upvotes: 2
Reputation: 691635
Make your StrategyFactory another Spring bean, and inject all the strategies in the factory:
@Component
public class StrategyFactory {
private final List<Strategy> strategies;
@Autowired
public StrategyFactory(List<Strategy> strategies) {
this.strategies = strategies;
}
public Strategy getStrategy(String strategyName) {
// iterate through the strategies to find the right one, and return it.
}
}
I usually use an enum rather than a String to identify the stratehy, and I make each Strategy return the enum value that it handles, so the iteration can be as simple as
return strategies.stream().filter(strategy -> strategy.getType() == type).findAny().orElseThrow(
() -> new IllegalStateException("No strategy found for type " + type));
Of course, you can also store the strategies in a Map inside the constructor, to make the lookup O(1).
Upvotes: 12