Strategy pattern with spring beans

david99world picture david99world · Jul 13, 2013 · Viewed 31.6k times · Source

Say I'm using spring, I have the following strategies...

Interface

public interface MealStrategy {
    cook(Meat meat);
}

First strategy

@Component
public class BurgerStrategy  implements
MealStrategy {
  @Autowired CookerDao cookeryDao;

  @Override
  public void cook(Meat meat) {
      cookeryDao.getBurger(meat);
  }
}

Next strategy...

@Component
public class SausageStrategy  implements
MealStrategy {
  @Autowired CookerDao cookeryDao;

  @Override
  public cook(Meat meat) {
      return cookeryDao.getSausage(meat);
  }
}

Context...

@Component
@Scope("prototype")
public class MealContext {
    private MealStrategy mealStrategy;

    public void setMealStrategy(MealStrategy strategy) {
        this.strategy = strategy;
    }

    public void cookMeal(Meat meat) {
        mealStrategy.cook;
    }
}

Now say this context was being accessed through an mvc controller, like...

@Autowired
private MealContext mealContext;

@RequestMapping(method = RequestMethod.POST)
public @ResponseBody Something makeMeal(Meat meat) {
    mealContext.setMealStrategy(new BurgerStrategy())
    mealContext.cookMeal(meat);
}

Should the context be a component? When I do I get an error saying loadOnStartup an there's a nonUniqueBean that the strategy could be, as you'd expect. Do all of the beans need to be components like above or are my annotations incorrect?

My biggest query really is can you use a context like that in a Spring MVC app? The problem I have with using @Scope(prototype) too is it means the cookeryDao calls in the strategies return a null pointer as the Dao's don't get injected.

How would I implement the above pattern using spring and also be thread safe? Is what I'm trying even possible?

Answer

pgiecek picture pgiecek · Nov 24, 2013

Since a concrete strategy is very often determined at run time based on the provided parameters or so, I would suggest something as follows.

@Component
public class BurgerStrategy implements MealStrategy { ... }

@Component
public class SausageStrategy implements MealStrategy { ... }

Then inject all such strategies into a map (with bean name as a key) in the given controller and select respective strategy on request.

@Autowired
Map<String, MealStrategy> mealStrategies = new HashMap<>;

@RequestMapping(method=RequestMethod.POST)
public @ResponseBody Something makeMeal(@RequestParam(value="mealStrategyId") String mealStrategyId, Meat meat) {
    mealStrategies.get(mealStrategyId).cook(meat);

    ...
}