Why static methods that instantiate? – insights from a n00b #2


 

This looks weird (if you're used to createObject) until you put two and two together and realize why the constructor method of a class has to have the same name as the class.

No sooner are we comfortable with this syntax, however, than we start delving into interesting real-world examples, and we start seeing lines of code like these:

    Runtime rt = Runtime.getRuntime() ;

    Parser parser = ParserFactory.makeParser( "MySaxParserClass" ) ;

    Parser parser = Class.forName( "oracle.xml.parser.v2.SAXParser" ) ;

This first thing you notice is that these are static class methods being invoked to return an object reference. (You should be able to tell these are static methods, because they are prefixed with the class name rather than an object reference variable. In other words, you can invoke them directly without first having to instantiate the class.)

There are really two independent techniques demonstrated in these examples,  although at first glance they look like the same thing.

1. Singleton classes

    Runtime rt = Runtime.getRuntime() ;

The first line of code is using a static method to return a handle to a singleton class. It's called "singleton" because the point of it is to only ever be instantiated once.(1) If it has been implemented correctly, you probably can't instantiate it using conventional syntax. (In the case of Runtime, it isn't too hard to see why it is a singleton!) 

The .getRuntime() method is returning a reference handle to the single, static,  pre-created Runtime object.

2. Hiding version-specific implementation

In working on the ADN service classes for Acxiom, I had to deal with the reality of writing an API to address two versions of the services simultaneously. I decided to handle this by subclassing each service class into two further version-specific implementations. As a result, my test code started looking like this:

    TEST_VERSION = 2 ;
    :
    if ( TEST_VERSION == 2 ) {
        oService = new AdnService_V2() ;
    }
    else {
        oService = new AdnService_V1() ;
    }

This looked ugly and clumsy, but I felt it was acceptable, because after all, it was just test code. This code was intended to test the methods of the common superclass AdnService, but that was ok because (by definition) both version-specific subclasses supported all the methods of AdnService.

It wasn't until I saw the line of code with the .getRuntime() call in it that something clicked and I realised that the same type of technique would work to improve my code. Here's the insight:

If I move the conditional instantiation code from my test program into a static method of the common parent class, not only would the version-specific class instantiation be hidden, but the reference returned to the test code could be of the correct data type!

This made too much sense to ignore. I added this method to the AdnService class:

    /**
     * factory method that returns a new instance of the service object.
     * @param version integer, (1= ADN 1.4, 2=ADN 2.x)
     */
    public static AdnService getInstance( int version  ) {

        if ( version == 2 ) {
            return new AdnService_V2() ;
        }
        else {
            return new AdnService_V1() ;
        }
    }

And now, the test code looks like this:

    :
    AdnService oService = AdnService.getInstance( TEST_VERSION ) ;

Much simpler, cleaner, and the reference is of the correct data type.


(1) There is an excellent discussion of implementing singleton classes by Dwight Deugo and Allen Benson published in the collection "Java Gems - Jewels from Java Report" edited by Dwight Deugo.(See Amazon.com).

Next insight...

Return to Article Index