Recipe 19.7. Implementing the Strategy PatternProblemYou want to apply the strategy pattern using AspectJ. SolutionThe strategy pattern provides a mechanism to separate client classes from the actual implementation details of a particular algorithm or strategy. Traditionally, all of the separate classes, which implement the strategy, implemented a distinct interface to allow the client to be decoupled from the different implementations. The abstract aspect in Example 19-13 uses the Director aspect-oriented design pattern (see Chapter 23) to provide a generic implementation of the strategy pattern that can be applied to your application. Example 19-13. Defining the strategy pattern using aspectspublic abstract aspect StrategyPattern { Hashtable strategyPerContext = new Hashtable( ); protected interface Strategy { } protected interface Context { } private Strategy Context.strategy = null; public void setConcreteStrategy(Context c, Strategy s) { strategyPerContext.put(c, s); } public Strategy getConcreteStrategy(Context c) { return (Strategy) strategyPerContext.get(c); } }DiscussionThe StrategyPattern abstract provides definitions of the Strategy and Context roles as interfaces. A hash table is used to look up the specific concrete strategy to be used. Figure 19-22 shows the structure of the StrategyPattern abstract aspect and the interfaces and behavior that it defines to support the strategy design pattern. Figure 19-22. The StrategyPattern aspect and the interfaces it defines for the design pattern's roles![]() Example 19-14 shows how the abstract StrategyPattern aspect could be applied for a specific application. The SortingStrategy aspect defines the mapping of the strategy role to two types of sorter implementation and the overall context to be that of a Sorter class. The advice is applied to override the sort method on the Sorter context to apply the appropriate sorting strategy. Example 19-14. Applying the StrategyPattern aspect to an applicationpublic aspect SortingStrategy extends StrategyPattern { declare parents : Sorter implements Context; declare parents : LinearSorter implements Strategy; declare parents : BubbleSorter implements Strategy; int[] around(Sorter s, int[] numbers) : call(int[] Sorter.sort(int[])) && target(s) && args(numbers) { Strategy strategy = getConcreteStrategy(s); if (strategy instanceof BubbleSorter) ((BubbleSorter) strategy).sort(numbers); else if (strategy instanceof LinearSorter) ((LinearSorter) strategy).sort(numbers); return numbers; } }The SortingStrategy aspect declares two different sorting strategies for a particular context, which is sorting. The actual strategy that is to be executed depends on the type of strategy that has been declared at runtime for a particular context. Figure 19-23 shows the Sorter, LinearSorter, and BubbleSorter classes before the SortingStrategy aspect is applied. Figure 19-23. The Sorter, LinearSorter, and BubbleSorter classes![]() Figure 19-24 shows the effects of applying the SortingStrategy aspect to the classes from Figure 19-23. Figure 19-24. The static structure after the strategy pattern has been applied to the Sorter, LinearSorter, and BubbleSorter classes![]() Figure 19-25 shows an example interaction with the Sorter, LinearSorter, and BubbleSorter classes using the aspect introduced strategy pattern features. Figure 19-25. Using the new strategy pattern characteristics of the Sorter and LinearSorter classes![]() See AlsoThe recipes in Chapter 16 contain more details on the mechanisms by which existing classes can be extended using aspects and the declare keyword; declaring and using abstract aspects are examined in Chapter 15; the args(Type or Identifier) pointcut is described in Recipe 11.3; the target(Type or Identifier) pointcut is examined in Recipe 11.2; the call(Signature) pointcut is covered in Recipe Recipe 4.1; exposing join point context is examined in Recipe 13.2; the Director aspect-oriented design pattern is explained in Recipe 23.3. |