Kontakt

Advanced Service Builders in Tapestry IoC: Strategy Pattern

Posted on Donnerstag, 20th Mai, 2010

As you might probably know Tapestry IoC is the heart and sole of Tapestry.  Almost every Tapestry feature is the result of the architecture based on Tapestry IoC. This is why Tapestry is so exciting and cool.

The ideas behind Tapestry IoC are so beautiful that I can’t live without them. In my daily job I’m using Tapestry IoC in non-web applications as well as in Tapestry applications. Occasionally I’m browsing the source code of open source or commercial Tapestry-based applications and wonder that some developers don’t use the best features of Tapestry IoC. I guess it’s because they are not aware of these features. That’s why I’ll publish a series of articles on advanced techniques to build services. The series starts with the Go4 Strategy Pattern. Let’s start with a very easy example.

Introduction to Strategy Pattern

Suppose you are writing an online shop such as Amazon. All the products are stored in a storage (such as a database). When you display a product to a customer you need to show the price of the product. Now imagine you are located in a country that has different sales tax rates for different goods. For example Germany has a 19% sales tax for non-book-goods. The sales tax for books and newspapers is only 7%. As you can see in the following example we need ugly if-else-statements when determining the sales tax rate of an item.

public class SalesTaxRateSourceImpl implements SalesTaxRateSource {

   public Double getRate(Item item) {

      if(item instanceof Book || item instanceof Newspaper) {
         return 1.07;
      }

      return 1.19;
   }
}

Let’s rewrite the implementation of SalesTaxRateSource service implementing the Gang of Four Strategy Pattern. The idea is to implement different strategies for calculation of sales tax rates. The strategies are:

  • DefaultSalesTaxRateSource: provides the sales tax rate of 19%
  • ReducedSalesTaxRateSource: provides the reduced sales tax rate of 7%

The implementations of the interfaces may look like follows.

public class DefaultSalesTaxRateSource implements SalesTaxRateSource{

   public Double getRate(Item item) {
      return 1.19;
   }
}
public class ReducedSalesTaxRateSource implements SalesTaxRateSource{

   public Double getRate(Item item) {
      return 1.07;
   }
}

Now we need to map these two implementations of the SalesTaxRateSource interface to certain implementations of the Item interface. This can be done in a contribute method for the SalesTaxRateSource service. Note that we map Book and Newspaper to ReducedSalesTaxRateSource. All the other implementations of Item are mapped to DefaultSalesTaxRateSource. This configuration is used to construct a strategy-based implementation of SalesTaxRateSource service.  For this purpose we create a StrategyRegistry from the contributed configuration inside the buildSalesTaxRateSource() method. The injected service StrategyBuilder is used to create an implementation of SalesTaxRateSource from StrategyRegistry on-the-fly.

The StrategyBuilder service creates an implementation which uses all the configured strategies behind the scenes. It executes all the ugly if-else-statements for you. It analyzes the first parameter of each service method to select the appropriate strategy. In our case we have only one service method with a single parameter.

public class AppModule {

    public static void contributeSalesTaxRateSource(
          MappedConfiguration<Class, SalesTaxRateSource> config) {

        config.addInstance(Item.class,
                           DefaultSalesTaxRateSource.class);

        config.addInstance(Book.class,
                           ReducedSalesTaxRateSource.class);

        config.addInstance(Newspaper.class,
                           ReducedSalesTaxRateSource.class);

	}

    public static SalesTaxRateSource buildSalesTaxRateSource(
            Map<Class, SalesTaxRateSource> configuration,
            StrategyBuilder strategyBuilder) {

        StrategyRegistry<SalesTaxRateSource> registry
                = StrategyRegistry.newInstance(
                    SalesTaxRateSource.class, configuration);

        return strategyBuilder.build(registry);
    }

}

You might ask yourself why you need this advanced service builder technique. You could manage different strategies in a simple map and just ask a strategy for a class. That’s almost correct but leaks one important feature StrategyRegistry offers. The lookup of strategies inside StrategyRegistry is based on an inheritance search. This is the reason why we mapped Item to DefaultSalesTaxRateSource. All Item implementations but Book and Newspaper are mapped to DefaultSalesTaxRateSource.

Now let me demonstrate the advantage of the inheritance-based strategy search. Imagine you extracted a common interface from Book and Newspaper: let’s say it is called Readable. Now the contribution can be simplified because you don’t need to map both Book and Newspaper to the same strategy. You only need to map their common interface:

public class AppModule {

    public static void contributeSalesTaxRateSource(
          MappedConfiguration<Class, SalesTaxRateSource> config) {

        config.addInstance(Item.class,
                           DefaultSalesTaxRateSource.class);

        config.addInstance(Readable.class,
                           ReducedSalesTaxRateSource.class);

	}
    ...
}

Real Life Example

Now that you know how to use Strategy pattern with Tapestry IoC, let’s see how this pattern is used inside Tapestry. As you might know event handler methods may have different return types. The value returned from an event handler method determines how Tapestry will render a response. This is accomplished by the ComponentEventResultProcessor service which is implemented as Strategy pattern. This service is built from a StrategyRegistry which is responsible for matching up a given return type of an event handler method with a registered strategy for that type. When Tapestry handles an event it analyzes the return type of the handler method and selects  a strategy to handle the returned type. The allowed return types are described here.

In the following example you can see a Demo page with two handler methods for the action event.

public class Demo {

    public Object onActionFromInternalLink() {
        return NextPage.class
    }

    public Object onActionFromExternalLink()
                throws MalformedURLException {
        return new URL("http://tapestry5.de/");
    }
}

The method onActionFromInternalLink() handles the event action that was fired by the component with id internalLink. The value returned by this method is a class of a page named NextPage. When this method is invoked, Tapestry will lookup a strategy to handle the result type java.lang.Class. The responsible strategy is the class ClassResultProcessor which just creates an internal link to a page and sends a redirect to that link. In case of method onActionFromExternalLink() Tapestry will find a strategy which is responsible to send a redirect to a URL.

Here is the simplified extract from TapestryModule:

public class TapestryModule {

    public void contributeComponentEventResultProcessor(
        MappedConfiguration<Class, ComponentEventResultProcessor>
                     configuration){

        configuration.add(
            URL.class,
            new ComponentEventResultProcessor<URL>() {
                public void processResultValue(URL value)
                           throws IOException
                    response.sendRedirect(value.toExternalForm());
            }
        });

        configuration.addInstance(
            Class.class, ClassResultProcessor.class);
    }
}

Summary

In this post I described how to implement Strategy Pattern using Tapestry IoC. Hopefully this post will help you to clean up your code by removing ugly if-else-statements. If you are not using Tapestry yet, you should give Tapestry IoC a try.

In the next post I’ll cover the Gang of Four Chain of Responsibility Pattern.

 

  1. Inge

 

avatar

Your name

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 

Tapestry 5 Blog - Copyright © 2009 - Eclectic Theme by Your Inspiration Web - Powered by WordPress