ringadingding
ringadingding

Reputation: 475

Strategy within Spring boot

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

Answers (5)

ravthiru
ravthiru

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

JEY
JEY

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

Bohdan Levchenko
Bohdan Levchenko

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

StanislavL
StanislavL

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

JB Nizet
JB Nizet

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

Related Questions