Saturday, February 11, 2012

@Work: Java Strategy Pattern

I'm putting together a set of posts on patterns or practices that I've found useful at work.  This is the first in that series. I covers how I used the Strategy Pattern to re-write an Application Programming Interface (API) that we use at work.

Background


Ironically, one of the constants of software engineering is change.  The point of developing Application Programming Interfaces (API) is to create tools that are adaptable and flexible enough to accommodate anticipated changes.

A good place to start, when determining how to best approach an API, is to look at aspects of it that change. What behaviors, actions, etc, are likely to change? Separate those behaviors and provide an interface for them.  In other words, take what varies and encapsulate it so it won't affect the rest of your code.

In the November/December 2009 Issue of Computer Magazine, an article titled "What Makes API's Hard to Learn? Answers from Developers",  by Martin P. Robillard conducted a study that concluded that the majority to problems developers have when learning new API's is documentation (we'll address this in another article), the other was complexity.

The approach taken in this redesign incorporates the Strategy Pattern, which is defined as:

Strategy Pattern:
·         Define a family of algorithms
·         Encapsulate each one
·         Make them interchangeable


Communication Manager


We are going to look at creating a communication manager.  This will allow developers to send data to and receive data from another system (perhaps a back end system or legacy system).

Problem Definition

We need a system that will send messages or commands to another system and receive messages or responses from that system. However, there are multiple systems with which we need to communicate. In addition, some of these systems do not always return data in the form that we need it.

Step 1: Define the Family of Algorithms


It is easy to see that what varies with regard to the connection components are:

·         The way they choose to send data

·         The way they choose to receive data (to a lesser extent)

Therefore, these behaviors are separated out and made into interfaces. 

public interface SendBehavior
{
Object sendData(Object obj);
}
public interface RecBehavior
{
Object recData(Object obj);
}

 Thus far we have abstracted out the class that will need to perform the behaviors (the calling class or connector class) and the behaviors that anyone who subclasses it will need.

Step 2: Encapsulate each one


Let's take a closer look at the main class our “caller”, so to speak.  We know it will need to perform the behaviors we have separated out.  So how do we handle that?  Check out the code below:

/**
*
ConnMan will employ the Strategy Pattern in that behavior will be taken out
*
of core classes and will allow for "plug-in"s rather than making changes to
*
the core code.
*

*/
public abstract class ConnMan {
SendBehavior
sendBehavior;
RecBehavior
recBehavior;
// Constructor
public ConnMan() {
}
// Setter for Sending Behavior
public void setSendBehavior(SendBehavior sendBehavior) {
this.sendBehavior = sendBehavior;
}
// Setter for Receiving Behavior
public void setRecBehavior(RecBehavior recBehavior) {
this.recBehavior = recBehavior;
}
/**
*
Perform Send Data (performSendData) This method will perform the sendData
*
method of the various classes that implement the SendBehavior interface.
*
We don't need to know which one as its behavior will be set by the
*
setSendBehavior method.
*/
public Object performSend(Object obj) {
return sendBehavior.sendData(obj);
}
/**
*
Perform Receive Data (performRecData) This method will perform the
*
recData method of the various classes that implement the RecBehavior
*
interface. We don't need to know which one as its behavior will be set by
*
the setRecBehavior method.
*/
public Object performRec(Object obj) {
return recBehavior.recData(obj);
}
}



3. Make them interchangeable


The implementations that will perform the "performSend" and "performRec" behaviors are separate and might look something like this:

public class JMSSend implements SendBehavior
{
/**
*
 sendData for JMSSend will format
* the incoming domain
 object into the
*format expected
for Java
* Messenger
Service and send it to a
*
pre-configured WebSphere message
* queue.


@Override
public Object sendData(Object obj) {
// TODO:
// 1. Convert domain object to XML
// 2. Send message
// 3. Retrieve Response
// 4. Return response as object
return "I used JMSSend with " + obj;
}
}
public class RecXML implements RecBehavior
{
/**
*
recData for RecXML will take the object returned from
* sendData
and convert it to (in this case) XML.
*

*/
@Override
public Object recData(Object obj) {
// TODO
// 1. Convert object received from sendData to XML
return "I used RecXML to convert sendData= " + obj + " to XML";
}
}

 Pretty simple right?  Okay, so now to pull this all together, they would simply extend the ConnMan abstract class to create the concrete class. Check this out:

public class ConnectionMgr extends ConnMan {
public ConnectionMgr() {
}
public void display() {
System.out.println("Send mechanism is " + sendBehavior.toString()
+ " and Receive Mechanism is " + recBehavior.toString());
}
}

A factory creates the objects:

public class ConnManFactory {
// Used to create instances of implementations. Instead of users
// using new to create the instances, the factory will do it based
// on their selection. Again, this is just for illustrative purposes,
// under true implementation, we would probably use something
// other than hardcoded values to find implementation choices
.

SendBehavior connManSendFact(ConnectionMgr cm, String simpl) {
SendBehavior ret_s;
ret_s = null;

if (simpl != null)
{
if (simpl == "JMS")
{
ret_s =
new JMSSend();
}
if (simpl == "XML") {
ret_s =
new XMLSend();
}
}
return ret_s;
}

RecBehavior connManRecFact(ConnectionMgr cm, String rimpl) {
RecBehavior ret_r;
ret_r =
null;
if (rimpl != null)
{
if (rimpl == "XML")
{

ret_r = new RecXML();
}
}
return ret_r;
}
}

Another solution you could use is to use a Properties or XML file and reflection and the .newinstance() method to create the objects on the fly.  You could simply have a setup file for your users that could be used to specify the implementation they'd like to use.  I'll cover this in detail in another article.
In short, it all comes together like this:

 

And to see how this works in action, let's create a small application class that uses it and see what happens:

public class AppTestConnMan {
/**
*
PROOF OF CONCEPT--Strategy Pattern for the Connection Manager
*
can use whatever send behavior or
*
receive behavior it wants to. In other words, the application sets the
*
behaviors (instead of selecting from a hard-coded list) allowing them the
*
flexibility to send the data in whatever format they want to and to
*
receive the data in whatever format they want.
*

* @param args
*/
public static void main(String[] args) {
ConnectionMgr cm = new ConnectionMgr();
/**
*
The send and receive behaviors are set by application developers The
*
implementations can be either implementations written by us or
*
written by the AppDev's...it's their choice. The API never needs to
*
know which behavior they choose to implement.
*

* In actual implementation, we could set it up so that the value for
*
impl could come from an XML (preferably) or Properties file, but for
*
the sake of this test, we'll just hardcode a string.
*

*/
ConnManFactory cmf = new ConnManFactory();
String simpl = "JMS";
String rimpl = "XML";
SendBehavior sret = cmf.connManSendFact(cm, simpl);
RecBehavior rret = cmf.connManRecFact(cm, rimpl);
cm.setSendBehavior(sret);
cm.setRecBehavior(rret);
// Or, they user can use their own implementation and specify it here:
// cm.setSendBehavior(new JMSSend());
// cm.setRecBehavior(new RecXML());
/**
*
The AppDev's create the domain object and sends it via the sendData
*
method that is in all implementations that extend
*
* The receive behavior can actually be optional. If the AppDev is
*
fine with receiving the data as an object, they need not go any
*
further. If, however, they would like the data converted to
*
something else, they can choose to use a receive behavior to
*
convert it.
*/
Object s_obj = "sent stuff"; // very simple domain object
Object me_sOut = cm.performSend(s_obj);
Object me_rOut = cm.performRec(me_sOut);
cm.display();
System.out.println("Output from performSend: " + me_sOut);
System.out.println("Output from performRec: " + me_rOut);
}
}

And here is the output from running this simple app:

Send mechanism is connMan2.JMSSend and Receive Mechanism is connMan2.RecXML
Output from performSend: I used JMSSend with sent stuff
Output from performRec: I used RecXML to convert sendData= I used JMSSend with sent stuff to XML

Simple, flexible, easy to maintain.  If someone wants to utilize their own implementation, they can by simply set another value for setSendBehavior value (even if it is one that they've written).

 I hope that this article has been a help to you. If you found it helpful or if you have something that you would like to add, feel free.

No comments: