


Du är här
2011-11-30 Designmönstret Strategi dopat med lite Spring
Jens Alm
Kan man kombinera en bok från 1994 med ett av dagens mest populära ramverk? Absolut. Här är ett exempel på hur man utnyttjar det bästa med båda.
För över 15 år sedan publicerades boken Design Patterns skriven av “Gang of Four”. Boken är fortfarande en av de mer inflytelserika böckerna när det gäller mjukvaruutveckling. Erich Gamma, Richard Helm, Ralph Johnson och John Vlissides var inte de första (och inte heller de sista) med att ta upp hur designmönster leder till bättre kod, men boken är fortfarande så populär att den hittills tryckts i 38 upplagor.
Designmönstret Strategi är ett sätt att gruppera algoritmer så att de kan varieras i runtime. Vi kommer titta på ett exempel på hur man kan variera koden beroende på den aktiva användarens data. Fullständig kod finns längst ned på sidan.
Grunderna
Det behövs två huvudsakliga delar för att använda vårt Strategimönster, en annotering och en factory.
Vi börjar med att titta på annoteringen vi behöver:
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Strategy {
Class type();
Profile[] profiles() default {};
}
Vad profiles används till kommer bli lite klarare när vi kollar på ett utdrag ur factoryn:
public <T> T getStrategy(final Class<T> strategy) {
final List<Object> strategyBeans = annotatedTypes.get(strategy);
final Object profileStrategy = findStrategyMatchingProfile(strategyBeans);
if (profileStrategy == null) {
throw new RuntimeException("No strategy found for type ["+strategy+"]");
}
return (T)profileStrategy;
}
private Object findStrategyMatchingProfile(final List<Object> strategyBeans) {
final Profile currentProfile = userService.getCurrentUser().getProfile();
Object defaultStrategy = null;
for (final Object bean : strategyBeans) {
final Strategy strategyAnnotation = strategyCache.get(bean.getClass());
if(currentProfile != null){
for (final Profile profile : strategyAnnotation.profiles()) {
if (profile == currentProfile) {
return bean;
}
}
}
if (isDefault(strategyAnnotation)) {
defaultStrategy = bean;
if(currentProfile == null) {
return defaultStrategy;
}
}
}
return defaultStrategy;
}
Detta är grunden till att kunna använda strategier. StrategyFactoryn använder några klasser till, User, Profile och UserService. Profile är i vårt exempel en enum som definierar FREE, LIMITED och PREMIUM.
Skapa en strategi
Ett enkelt exempel på en strategi är till exempel navigering. Vi börjar med ett enkelt interface:
public interface NavigationStrategy {
void createNavigation(ModelAndView modelAndView);
}
För att sedan skapa en strategi för FREE och LIMITED gör man bara följande:
@Component
@Strategy(type=NavigationStrategy, profiles={Profile.FREE, Profile.LIMITED})
public class FreeAndLimitedNavigationStrategy implements NavigationStrategy {
public void createNavigation(ModelAndView modelAndView) (
// Skapa navigering och lägg till den i modelAndView
}
}
Om vi lägger till profiles=... på annoteringen så matchar den mot användarens profil. Om ingen profiles finns angiven i annoteringen är den automatiskt default om kundens profil inte matchar någon annan strategi.
Använda en strategi
För att använda vår nya strategi behöver vi bara använda autowire i någon klass för att få en referens till StrategyFactory.
@Autowire
private StrategyFactory strategyFactory;
public void someMethodInSomeServiceOrController(ModelAndView modelAndView) {
NavigationStrategy navigationStrategy = strategyFactory.getStrategy(NavigationStrategy.class);
navigationStrategy.createNavigation(modelAndView);
}
Fördelarna
Det vi vunnit på att göra det här är att vi slipper nästlade if-satser för att ta reda på olika användares förutsättningar och att det är väldigt lätt att testa de olika implementationerna separat. Ett test för FREE och LIMITED samt ett test för PREMIUM vilka är betydligt enklare än att skriva ett kombinerat test med alla mockar på ett ställe.
Det finns mängder med olika användningsområden för strategier, tex när man har olika krav beroende på vilket land användaren är i eller kanske om användaren surfar i en vanlig browser eller använder en smartphone.
Fullständig kod till exemplet ovan finns att ladda ned här: strategy.zip
