Don't Use Static Fields or Methods!

COMP 310    Java Resources  Eclipse Resources

In general, the only places you should ever be using a static field are:
  - Singleton objects
  - serialVersionUID and other final values
  - Null Objects, including invariant error and similar objects

The only static method should be main().

A Problem of Invariance:

The use of static fields or methods in good object-oriented programming is rare because they create invariant behaviors at the class level without the benefits of being an object. For instance, a class defined with static methods cannot be passed as an input parameter to a method, i.e treated as a first-class entity in the system. That means that the entity represented by taht class cannot ever be treated as a variant by any other part of the system.

A typical scenario where static methods are used is in defining a class that provides a library of processing algorithms, such as mathematical formulas. Many resources advocate implementing such a class with static methods. The entire system is required to invoke the library's methods by calling classname.method(). Another part of the system cannot treat the library as a variant and thus are unable to have the freedom to dynamically use different implementations of the library. A better solution is to use a define the library abstractly with a top-level interface and then use a Singleton Design pattern to embody a specific implementation of the library. The rest of the system is typed to the library's interface, thus freeing them from any specific implementation. The Singleton pattern maintains efficiency by only instantiating a single object for any given implementation and can be passed as a parameter to any entity that needs to use the library. Multiple implementations of the library are easily possible and can be used in the system without disrupting any other parts of the system.

Overriding a static method is fraught with problems because it creates a semantic nightmare where the method means one thing at one level of the inheritance hierarchy and another at a different level. Overriding of concrete methods should always be avoided.

Unexpected Behaviors:

static fields are problematic unless they are final and are primitive or standard values, i.e. boolean, int, double, String, etc.. Mutable static fields can cause unexpected behaviors in a system because there isvery difficult to tell what entities are able to modify the field and when they are doing it. This is similar to the reasons why public fields in classes are never a good idea but even worse because it is at a class-wide level, not just an object instance level.

In enterprise systems where operational code is spread over multiple computers, class-level invariance cannot be enforced across machines. A mutated static field only affects one instance of the code and thus that value may be different in other parts of the system, leading to unpredictable results. Mutable static fields are essentially death to enterprise systems and should never be used.

 

A Common Scenario: Using static arrays of strategies

Here is a scenario that pops up in student code where a composite strategy is being defined:

public class MyCompositePaintStrategy extends MultiPaintStrategy {
 
    private static APaintStrategy[] myPStrats = {new EllipsePaintStrategy(), new RectanglePaintStrategy()};
    
    public  MyCompositePaintStrategy() {
       super(new AffineTransform(), myPStrats);
    }
 
// etc …
}

where there is a composite strategy defined along these lines:

public class MultiPaintStrategy  extends APaintStrategy {
 
    public APaintStrategy[] pStrats;
 
    public MultiPaintStrategy(AffineTransform at, APaintStrategy[] pStrats) {
         super(at); 
         this.pStrats = pStrats;
    }
 
// etc …
}

 

There are two fixes to the above code, one fast and the other better.   Both involve getting rid of the static field in MyComposite:

Quick fix:   dynamically allocate the array without using a field or variable.

public class MyCompositePaintStrategy extends MultiPaintStrategy {
 
    public  MyCompositePaintStrategy() {
       super(new AffineTransform(), new APaintStrategy[]{new EllipsePaintStrategy(), new RectanglePaintStrategy()});
    }
 
…etc
}

 

Better fix:   Change MultiPaintStrategy to take a vararg

public class MultiPaintStrategy  extends APaintStrategy {
 
    public APaintStrategy[] _pStrats;
 
    public MultiPaintStrategy(AffineTransform at, APaintStrategy… pStrats) {
         super(at); 
         _pStrats = pStrats;
    }
 
// etc …
}

Notice that that the only change is to replace a “[]” with “” in the MultiPaintStrategy constructor's signature.  Nothing else changes because the type of pStrats was and still is APaintStrategy[].    See the discussion on vararg parameters at the bottom of Lec12.

The effect of this is to simplify MyCompositePaintStrategy considerably:

public class MyCompositePaintStrategy extends MultiPaintStrategy {
 
    public  MyCompositePaintStrategy() {
       super(new AffineTransform(), new EllipsePaintStrategy(), new RectanglePaintStrategy());
    }
 
//etc… 
}

 

Another Common Scenario: Using static arrays of choices

Sometimes students use a static field to hold the options for classes that randomly choose an image or the like, for example like such:

public class MyRandomImagePaintStrategy extends ImagePaintStrategy {

	private static String[] imageFiles = {"images/humbird_animate.gif","images/sheep_animate.gif"}; // array of file names.

	public BirdSheepImagePaintStrategy() {
		super(imageFiles [Randomizer.Singleton.randomInt(0, 2)], 0.5);
	}
}

One solution is to use Java's "ternary conditional operator" or simply, the "?" operator:

public class MyRandomImagePaintStrategy extends ImagePaintStrategy {

	public MyRandomImagePaintStrategy() {
		super( ((Math.random() < 0.5)?"images/humbird_animate.gif" : "images/sheep_animate.gif") , 0.5);
	}
}

For the more general case, where more than 2 choices are desired, use a dynamically created array as was done above and pick a random element from it:

public class MyRandomImagePaintStrategy extends ImagePaintStrategy {

	public MyRandomImagePaintStrategy() {
		super( (new String[]{"images/humbird_animate.gif", "images/sheep_animate.gif", "images/Mario_animate.gif", "images/Sonic_animate.gif"})[Randomizer.Singleton.randomInt(0, 4)], 0.5);
	}
}

An even better solution is to recognize the process of randomly choosing a filename has nothing to do with paint strategies and thus define a new method of IRandomizer and Randomizer to randomly pick an element from a vararg input list:

public interface IRandomizer {
	// other methods elided
	
	/**
	 * Return one of the vararg input parameters with equal probability.
	 * The input parameters must all be of the same type and the return type 
	 * will match that type.
	 * @param <T> The type of the input parameters and thus the return type
	 * @param choices vararg list of choices 
	 * @return One of the input parameters
	 */
	public abstract <T> T randomChoiceList(@SuppressWarnings("unchecked") T... choices );   // Suppress the warning about a generic vararg
}

where Randomizer implements the method as such:

public class Randomizer implements IRandomizer {

	// other methods elided
	
	/**
	 * Return one of the vararg input parameters with equal probability.
	 * The input parameters must all be of the same type and the return type 
	 * will match that type.
	 * @param <T> The type of the input parameters and thus the return type
	 * @param choices vararg list of choices 
	 * @return One of the input parameters
	 */
	@Override
	public <T> T randomChoiceList(@SuppressWarnings("unchecked") T... choices) {    // Suppress the warning about a generic vararg
		return choices[randomInt(0, choices.length-1)];
	}
}

The constructor for the MyRandomPaintStrategy paint strategy is now very clear and clean:

public class MyRandomPaintStrategy extends ImagePaintStrategy {

	public MyRandomImagePaintStrategy() {
		super( Randomizer.Singleton.randomChoiceList("images/humbird_animate.gif", "images/sheep_animate.gif", "images/Mario_animate.gif", "images/Sonic_animate.gif"), 0.5);
	}
}

 

 

 

© 2020 by Stephen Wong