Sunday, April 7, 2013

Spring: implementing the Factory pattern

Although Spring, in itself, is already an example implementation of the Factory pattern (any Spring application's application context is just a giant Factory, right!), from time to time, we would like to implement this same pattern in our application logic just to keep our code clean and tidy. Let's see how we can do this, by a short example:

1 Defining an interface for the classes to be instantiated through the factory
Let's say we're working on a Spring application that handles document printing. For a given document, it is able to print it in either A4 or A5 format and either portrait and landscape layout. Each of these printing strategies extends a common interface :

package strategy;

import model.Document;

public interface IPrintStrategy {
 public void print(Document document);
}

Here are the concrete implementations for each printing strategy. To keep this example simple, each concrete printing strategy will only indicates what it is supposed to to:


package strategy;

import model.Document;

import org.springframework.stereotype.Component;

@Component("A4Landscape")
public class PrintA4LandscapeStrategy implements IPrintStrategy{

 @Override
 public void print(Document document) {
  System.out.println("Doing stuff to print an A4 landscape document");
 }

}


package strategy;

import model.Document;

import org.springframework.stereotype.Component;

@Component("A5Landscape")
public class PrintA5LandscapeStrategy implements IPrintStrategy{

 @Override
 public void print(Document document) {
  System.out.println("Doing stuff to print an A5 landscape document");
 }

}


package strategy;

package strategy;

import model.Document;

import org.springframework.stereotype.Component;

@Component("A4Portrait")
public class PrintA4PortraitStrategy implements IPrintStrategy{

 @Override
 public void print(Document document) {
  System.out.println("Doing stuff to print an A4 portrait document");
 }

}



package strategy;

import model.Document;

import org.springframework.stereotype.Component;

@Component("A5Portrait")
public class PrintA5PortraitStrategy implements IPrintStrategy{

 @Override
 public void print(Document document) {
  System.out.println("Doing stuff to print an A5 portrait document");
 }

}

For now, just note that each printing strategy interface is annotated as being a Spring component with a certain name. 

2 Defining the factory class interface
Now, we'll have to define an interface that is to be implemented by the factory class through which we will retrieve printing strategies:


package strategy;

public interface PrintStrategyFactory {
 
 IPrintStrategy getStrategy(String strategyName);
}

As you can see, the factory class will get a strategy name as input and will return an instance of IPrintStrategy.

3 Defining the Spring application configuration




 
 
  
  
 
 
 
 
 
 
 


In this configuration file:
  • we specify the package to scan for spring component detection (here, it's the "strategy" package)
  • we declaratively define a bean of type "ServiceLocatorFactoryBean" that will be the Factory.
  • we also define, and that's optional, name aliases, so that a same printing strategy could be retrieved either by the name specified in the @Component annotation, or by the alias.
4 Testing our example
With the following test class, we can check that everything works as expected:


import model.Document;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.Test;

import strategy.PrintStrategyFactory;


@ContextConfiguration(locations = {"classpath:/spring-config.xml"})
public class SpringFactoryPatternTest extends AbstractTestNGSpringContextTests{
 
 @Autowired
 private PrintStrategyFactory printStrategyFactory;
 
 @Test
 public void printStrategyFactoryTest(){
  Document doc = new Document();
  
  printStrategyFactory.getStrategy("DEFAULT").print(doc);
  printStrategyFactory.getStrategy("A5L").print(doc);
  printStrategyFactory.getStrategy("A5P").print(doc);
  printStrategyFactory.getStrategy("A5Portrait").print(doc);
 }
}

For instance, when we ask the factory class to give us the printing strategy answering to the name "DEFAULT", we'll end up with an instance of  "PrintA4PortraitStrategy" as "DEFAULT" is an alias for the spring component with name "A4Portrait". 

From there, I believe you got how it works. If not, feel free to checkout the source code and execute the test class, and you'll grasp the whole concept in the blink of an eye.

5 Source code
The whole source code of this example is available here (github)

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.