24 July 2007

A Technique for Faster Development

Take a look at this:

import static java.lang.Integer.parseInt;
import static java.lang.System.out;

import java.lang.reflect.Method;

public class DynamicMethods
{
public int operation(String op,
int a,
int b) throws Exception
{
Class<? extends DynamicMethods> c = this
.getClass();
Class<?>[] argtypes = {
int.class, int.class};
Method m = c
.getDeclaredMethod(op,
argtypes);
Object[] args = {a, b};
int result = (Integer) m
.invoke(this, args);
return result;
}

int add(int a, int b)
{
return a + b;
}

public static void main(String[] args)
throws Exception
{
DynamicMethods dm = new DynamicMethods();
int c = dm.operation(args[0],
parseInt(args[1]),
parseInt(args[2]));
out.println("Result = " + c);
}
}


Look first at the operation() method. It takes a String ("op") and locates a Method within this class that has that name and takes two ints as arguments. It then calls that method, passing in the arguments it was given.

Now, look at the main(). The first argument is used as the op value, while the other two are parsed as integer values and passed as the other two arguments in a call to operation().

So, if we run this little program with the arguments "add 4 8", operation() calls add(), passing 4 and 8 as arguments. add() returns the sum (12), which is printed out by main().

This may seem trivial but it is the core of a powerful technique that I've found can reduce development time significantly on certain types of projects. Here's the important part: let's say I want to add a "multiply" operation. All I need to do is add the new method...

int multiply(int a, int b)
{
return a * b;
}


...and I'm done. Now I can run the application again with "multiply 4 8" as arguments and it works right away. I don't need to add lookup table entries, or change some XML file to configure in the new function. I can add as many new operations as I like just as easily - all I have to do is make sure that the new methods have a signature that has this pattern:

int operationName(int a, int b)
{
...
}


Here's an example with a bit more meat. Recently I developed a small web application to run in Tomcat. It only needed about four different page layouts, so I didn't see the need to mess around with the complication of Struts, JSPs and Action Classes (in my opinion Struts introduces way more complication than is necessary to do what it does anyway, and this only gets worse as the projects get larger). Instead, the HTML is generated from Velocity templates.

Tomcat is configured to pass all URIs with pathnames ending in "*.do" to the application's one and only servlet. Here is the servlet's service() method (with try/catch blocks removed for clarity):

public void service(ServletRequest request,
ServletResponse response)
throws ServletException,
IOException
{
// Cast req/resp to more useful types
HttpServletRequest rq =
(HttpServletRequest) request;
HttpServletResponse rs =
(HttpServletResponse) response;

// Get the URI, strip off the leading '/'
// and the ending ".do"
String uri = rq.getServletPath();
String methodName = uri.substring(1,
uri.indexOf(".do"));

// Find the right method
Class<? extends MyServlet> c = this.getClass();
Class<?>[] argTypes = {
HttpServletRequest.class,
HttpServletResponse.class};
Method method = c.getDeclaredMethod(methodName,
argTypes);

// Call the method, get back a Map
Object[] args = {rq, rs};
Map<String, Object> m =
(Map<String, Object>) method.invoke(this, args);

// The map contains the required Velocity
// template name
String templateName =
(String) m.get("templateName");

// Use the map as a Velocity Context and merge -
// output streams to the client browser
VelocityContext ctx = new VelocityContext(m);
velocityEngine.mergeTemplate(templateName,
ctx,
response.getWriter());
}
}


Let's say I need to add a "/menu.do" operation. All I need do in the servlet is add one method:

Map<String, Object> menu(HttpServletRequest request,
HttpServletResponse response)
{
...
}


This method performs whatever business logic is required and returns a Map that can be used as a Velocity context. The map must also contain a key, "templateName" identifying the Velocity template that the context is to be merged with. service() takes care of the rest.

This isn't a 100% ideal solution because part of the presentation logic (building the map) is in the methods along with the business logic - but the actual rendering is controlled by the template, so the methods don't need to mess with that. But for small applications I've found this is an excellent approach.

Labels:

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]



<< Home