Monday, December 23, 2013

Springify The Strategy Pattern - Part 2


I talked about how to implement strategy pattern in springapplication in my previous post. Here I want to show you how Spring Annotations can make our lives easier to implement these patterns.
Spring 2.5 version introduced way to configure the dependency injection using annotations. This eliminates all those XML files in an application that just defined beans and bean wiring. Now annotation allows you to configure these inside the bean (component).

Some of the annotations that I will be using were @Autowired, @Resource, @Component, @Service etc.
All right let’s get into the code, I will take the second approach from the previous article and configure it using annotation. The new configuration XML looks like this,

<context:component-scan base-package="xyz.xyz.strategy"/>

What happened to all bean definitions? What is this component scan?
Well annotation based configuration eliminated the bean definition in the XML. Instead we enable spring auto component scanning of components in the base package where it can scan, detect, instantiate and wire all these beans (components).

Now let’s look at the beans again. You will have to go back to your strategy implementation classes like RegularShippingCostStrategyImpl etc. and add @Service or @Component annotation to the class as shown below,
@Service
public class RegularShippingCostStrategyImpl implements ShippingCostStrategy {
//calculation code
}

@Service
public class PremiumShippingCostStrategyImpl implements ShippingCostStrategy {
//calculation code
}

@Service
public class PrimeShippingCostStrategyImpl implements ShippingCostStrategy {
//calculation code
}

 
Your ShippingCostService class with added annotation below,
@Service
public class ShippingCostService {
 
 @Autowired
 private ShippingCostContext shippingContext = null;
  
 public double calculateShipping(Item item, String memberStatus){
  double cost = shippingContext.calculateShipping(item, memberStatus);
     return cost;
 }
 
}

 
Now look at this strategy context class,
@Component
public class ShippingCostContext {
 
 @Autowired
 private PrimeShippingCostStrategyImpl primeShippingStrategy = null;
 
 @Autowired
 private PremiumShippingCostStrategyImpl premiumShippingStrategy = null;
 
 @Autowired
 private RegularShippingCostStrategyImpl regularShippingStrategy = null;
        
 public double calculateShipping(Item item, String memberStatus){
       double cost = 0.0;
       if(memberStatus.equalsIgnoreCase(StrategyConstants.MEMBER_PRIME))
          cost = primeShippingStrategy.calculate(item);
       else if(memberStatus.equalsIgnoreCase(StrategyConstants.MEMBER_PREMIUM))
          cost = premiumShippingStrategy.calculate(item);
       else if(memberStatus.equalsIgnoreCase(StrategyConstants.MEMBER_REGULAR))
          cost = regularShippingStrategy.calculate(item);
    
       return cost;
 }
   
}

Now run the same test to check this out.
The same can be achieved for the first approach as well by simply switching auto wire strategy implementation classes from strategy context class to service class.
About the third approach is not all required since spring auto scan and auto wire simplifies the implementation already. But in case if you are interested here is how you can do it.

<context:component-scan base-package="xyz.xyz.strategy"/>
   
<util:map id="strategies" map-class="java.util.HashMap">
       <entry>
         <key>
           <util:constant static-field=" xyz.xyz.strategy.StrategyConstants.MEMBER_REGULAR"/>
          </key>
            <ref bean="regularShippingCostStrategyImpl" />
         </entry>
         <entry>
             <key>
              <util:constant static-field=" xyz.xyz.strategy.StrategyConstants.MEMBER_PREMIUM"/>
             </key>
             <ref bean="premiumShippingCostStrategyImpl" />
         </entry>
         <entry>
             <key>
              <util:constant static-field=" xyz.xyz.strategy.StrategyConstants.MEMBER_PRIME"/>
             </key>
             <ref bean="primeShippingCostStrategyImpl" />
         </entry>
   </util:map>

The XML context will have to define the spring util Map with key and ref value as shown above. You will then have to Autowire the map into the strategy context like this,
@Service
public class ShippingCostContext {
 
 @Resource
 private Properties strategies;
 
 public double calculateShipping(Item item, String memberstatus){
        double cost = 0.0;
        if(strategies.containsKey(memberstatus)){
         ShippingCostStrategy shippingCostStrategy = (ShippingCostStrategy)strategies.get(memberstatus);
         if(shippingCostStrategy != null)
          cost = shippingCostStrategy.calculate(item);
        }
               
 return cost;
 }


That’s all, the strategy pattern is now spring annotated. The spring annotations are quite powerful and simplify the configuration.