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
-
and other serialVersionUID
final
values
- Null Objects, including invariant error and similar objects
The only static
method should be main()
.
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.
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.
static
arrays of strategiesHere 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
:
public class MyCompositePaintStrategy extends MultiPaintStrategy { public MyCompositePaintStrategy() { super(new AffineTransform(), new APaintStrategy[]{new EllipsePaintStrategy(), new RectanglePaintStrategy()}); } …etc }
MultiPaintStrategy
to take a
varargpublic 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… }
static
arrays of choicesSometimes 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