04.05.08

A much better Strategy Pattern - Part II

Posted in Design Patterns at 1:29 am by Administrator

So in the last post i talked about how i could make it better with slight twist. Well for those of you who don’t know what Dependency Injection is read :
Dependency Injection By Fowler. Lets get started. To give you a little refresher, In my last post we discussed, Strategy pattern briefly (refer to : Strategy Pattern - Part I), we create an IStrategy interface ( with operation method in it, taking two parameters (arguments at runtime). and implemented couple of simple operation-al strategies like SumStrategy (which performs sum in operation implementation), similarly we created MultiplicationStrategy, DivisionStrategy etc. We had a Context on which we set the strategy (context.setStrategy(IStrategy strategy)) and voila we are done, we can isolate each algorithm to its own object.

As may be evident from above, this is not completely flexible approach. Especially because i am doing (as a client) something like context.setStrategy(new DivisionStrategy) or something in those lines. So client “knows” about the the strategy really; and our code is tied up in that sense …

So first lets setup Dependency Injection container. a method setUp( ) demonstrates that below.


public static void setUp(Class<? extends IStrategy> ... strategies){

for(Class<? extends IStrategy> strategy : strategies){

System.out.println("( Strategy Interface ) ---> ( " + strategy.getSimpleName() + " )");

// prepare the type Map ...
typeMap = new StrategyTypeMap();

// prepare the value list
List

valueList.add(DivisionStrategy.class);
valueList.add(MultiplicationStrategy.class);
valueList.add(SumStrategy.class);

// configuration complete.
typeMap.configureTypeMap(strategy, valueList);

}

}

Lets break the code down in steps and look at it closely. In setting our CustomDIContainer, we use a typeMap (we’ll talk about that in a bit), we simply loop through classes passed to the method, add the classes to list and finally insert the entry in the Map. This map is a specialized Type map which takes in a Key (which in our case is IStrategy Interface) and a valueList in form of Classes that implement the Key Interface. So we have strategies that are being added here.


public static IStrategy getImplementation(Class<? extends IStrategy> key, String typeToGet){

// notice how i pass things around here ... be careful ... read this before you proceed!
return instantiateImplementation(getAppropriateImplementation(key, typeToGet));
}

private static Class<? extends IStrategy> getAppropriateImplementation(Class<? extends IStrategy> key, String typeToGet){

List>Class<? extends IStrategy>> searchList = getCollection(key);

if(typeToGet.trim().equalsIgnoreCase("DIVISION")){
Class<? extends IStrategy> returnType = null;

for(Class<? extends IStrategy> classType : searchList){

if(classType.isAssignableFrom(DivisionStrategy.class)){
returnType = classType;

}

}

// return
return returnType;

}else if(typeToGet.trim().equalsIgnoreCase("SUM")){
Class<? extends IStrategy> returnType = null;

for(Class<? extends IStrategy> classType : searchList){

if(classType.isAssignableFrom(SumStrategy.class)){
returnType = classType;

}

}

// return
return returnType;

}else if(typeToGet.trim().equalsIgnoreCase("MULTIPLICATION")){
Class<? extends IStrategy> returnType = null;

for(Class<? extends IStrategy> classType : searchList){

if(classType.isAssignableFrom(MultiplicationStrategy.class)){
returnType = classType;

}

}

// return
return returnType;

}else{

// return finally
return DivisionStrategy.class;
}

private static IStrategy instantiateImplementation(Class<? extends IStrategy> key){

IStrategy returnStrategy = null;

try {

returnStrategy = key.newInstance();

} catch (InstantiationException e) {
e.printStackTrace();

} catch (IllegalAccessException e) {
e.printStackTrace();

}

// return
return returnStrategy;
}

^^ Wow this is a huge block of code to digest; but lets break it into pieces and make it a little easy. Basically we are trying to get Implementation for the IStrategy interface we pass in (It could have been other interface as well, but lets not worry about it at the moment). Now :

  1. We get an appropriate Implementation by looping through the list of classes for a key (IStrategy in our case) and using String passed in (like DIVISION, MULTIPLICATION) etc to distinguish between the values (that is Implementation classes) and return one. So like for example in first If construct we search for a DIVISION key (Client has to perform DIVISION so Division IT WILL! :) ), we check if (one by one in the loop for all values) whether its assignable to DivistionStrategy.class or not, if so, we simply return the class type, same goes for SUM and other operations.
  2. in instantiate, we simply create instance of the class (apparently) and return back. Notice, though that return type and parameter types are IStrategy and ? extends IStrategy class, so for client its just IStrategy or any interface like that for that matter and simply the String giving container the type of operation it wants to perform.

Next, we populate our good old Context;


public static Context populateContext(String typeToGet){

Context context = Context.getInstance();
context.setStrategy(getImplementation(IStrategy.class, typeToGet));

// return
return context;
}

Finally here is our much improved TestWithDI (compare this with Strategy Pattern Part - I example and you will know what i meant when i said, cleaner approach).


public static void main(String[] args) {

// SETUP THE DI CONTAINER
CustomDIContainer.setUp(IStrategy.class);

// define context using SUM
Context context = CustomDIContainer.populateContext("SUM"); // <-- Notice how we do a SUM! no more new SumStrategy in setter of context ...
Integer result = (Integer)context.getStrategy().operation(400, 200);
System.out.println("( Result of the SUM with DI was ) ---> ( ” + result + ” )”);

// define context using Multiplication
// Notice how we do a MULTIPLICATION! No more new Multiplication in setter of context …
Context multiplicationContext = CustomDIContainer.populateContext(”MULTIPLICATION”);
Integer multiplicationResult = (Integer)multiplicationContext.getStrategy().operation(400, 200);

//log
System.out.println(”( Result of the MULTIPLICATION with DI was ) —> ( ” + multiplicationResult + ” )”);

// define context using Dviision
// Notice how we do a DIVISION! no more new SumStrategy in the setter of context …
Context divisionContext = CustomDIContainer.populateContext(”DIVISION”);
Integer divisionResult = (Integer)divisionContext.getStrategy().operation(400, 200);

//log
System.out.println(”( Result of the DIVISION with DI was ) —> ( ” + divisionResult + ” )”);

}

Finally note that in setUp( ) method we Will have to add our Interfaces and implementations for our Oh so Hot DI container ;) . We can move this configuration to XML too, although XML approach can be unreliable because if i or any other developer removes any thing (any interface or alike implementations) your code will fail. (We’ll explore the DI concept in later posts). Also this Dependency injection is not a full dependency injection demonstration, as you might notice that i am using populateContext explicitly, normally i should be able to do something like context.load() which will load and wire all dependencies for me, which if i add Context as a bean in our setUp of DI container, i could wire the context attribute for implementing strategy very easily, (As a rough idea to you guys (I will explore this concept later on), you add context to a list of dependencies managed by the container, when container loads the Class, it finds that it has an outside dependency, so it will run a check on its list, if it has the class type which is required to populate this setter field, it will do so. I wanted to make concept more clear so i didnt add those things up here.

Next we will take a look at Improvising our visitor pattern in removing “accept” method and making it more flexible through Generics. ( For continuation purpose; here’s the link:
Visitor Pattern - Part I. Speaking of generics, we can use Generics to improve the above code too … but thats for another time of the day.

Stay Tuned ! and thanks for your visits. :)

Regards
Vyas, Anirudh

Leave a Comment