Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Designing object initialization

Ensure proper initialization of your objects at all times

  • Print
  • Feedback

Page 5 of 5

// In Source Packet in ex7/Example2.java
class Example2 {
    public static void main(String[] args) {
        // Create a "tall" coffee cup filled with 50 ml coffee.
        CoffeeCup cup1 = new CoffeeCup(CoffeeCup.TALL, 50);
        // Create an empty "short" coffee cup.
        CoffeeCup cup2 = new CoffeeCup(CoffeeCup.SHORT);
    }
}


This version of class CoffeeCup requires that the client programmer specify a cup size in both constructors. It doesn't have a no-arg constructor.

The fourth approach
One other option you could offer a client programmer is to create a CoffeeCup object with no specific size. Here, you would allow a client programmer to create a CoffeeCup object with a default constructor, but on creation the object would not be "valid" because it wouldn't have a valid size. Later, that programmer would have to invoke a method to give the CoffeeCup object a proper size. Until it was given a proper size, the CoffeeCup object would be "invalid" and could not be used. Here is how you would implement this scheme:

// In Source Packet in ex8/InvalidObjectException.java
class InvalidObjectException extends Exception {
}
// An illustration of approach 4
class CoffeeCup {
    public static final int SHORT = 0;
    public static final int TALL = 1;
    public static final int GRANDE = 2;
    public static final int MAX_SHORT_ML = 237;
    public static final int MAX_TALL_ML = 355;
    public static final int MAX_GRANDE_ML = 473;
    private boolean sizeValid;
    private int size;
    private int innerCoffee;
    // Only three constructors defined in this
    // version of CoffeeCup
    public CoffeeCup() {
    }
    public CoffeeCup(int size) {
        this(size, 0);
    }
    public CoffeeCup(int size, int startingAmount) {
        if (startingAmount < 0) {
            String s = "Can't have negative coffee.";
            throw new IllegalArgumentException(s);
        }
        if (startingAmount > getMaxAmount(size)) {
            String s = "Too much coffee.";
            throw new IllegalArgumentException(s);
        }
        setSize(size);
        innerCoffee = startingAmount;
    }
    public final void setSize(int size) {
        if ((size != SHORT) && (size != TALL)
            && (size != GRANDE)) {
            String s = "Invalid cup size.";
            throw new IllegalArgumentException(s);
        }
        sizeValid = true;
        this.size = size;
    }
    private static int getMaxAmount(int size) {
        int retVal = 0;
        switch (size) {
        case SHORT:
            retVal = MAX_SHORT_ML;
            break;
        case TALL:
            retVal = MAX_TALL_ML;
            break;
        case GRANDE:
            retVal = MAX_GRANDE_ML;
            break;
        default:
            String s = "Invalid cup size.";
            throw new IllegalArgumentException();
        }
        return retVal;
    }
    public int add(int amount) throws InvalidObjectException {
        if (!sizeValid) {
            throw new InvalidObjectException();
        }
        innerCoffee += amount;
        int max = getMaxAmount(size);
        int spillAmount = 0;
        if (innerCoffee > max) {
            spillAmount = innerCoffee - max;
            innerCoffee = max;
        }
        return spillAmount;
    }
    //...
}


In this example, sizeValid indicates whether or not instance variable size has been set to a proper value. If the object is constructed with either of the two constructors that include a size in their parameter list (and the passed size is valid), sizeValid is set to true. If the object is created with the no-arg constructor, the size is not specified and sizeValid is left at its default initial value, false. Later, the size variable can be set to a proper value via the setSize() method, which also sets sizeValid to true.

This is not a very appropriate design for class CoffeeCup. Although you will probably encounter some situations in which it makes sense to design a class such that its objects can at times be invalid, generally you should avoid this approach. As a guideline, you should attempt to design classes such that their instances are always valid, from the beginning to the end of their lifetimes. To follow this guideline, you either initialize instance variables to default proper values or require that the client programmer provide data in constructor parameters from which you can calculate proper initial values.

Objects that can have invalid states are harder to use and harder to understand than those that are always valid. Usually, when an object is in an invalid state, many of its methods won't work. In the example above, the add() method doesn't work when the CoffeeCup doesn't have a valid size. Here, the add() method uses both the size and innerCoffee variables to determine how much more coffee can legally be added to the cup. If the amount of coffee passed as a parameter to add() plus the amount of coffee already in the cup exceeds the maximum capacity of the cup, the add() method will "spill" the extra coffee back to the caller through its return value. When sizeValid is false, the add() method can't determine the maximum capacity of the cup, because it doesn't know the cup's size. Hence, the add() method won't work when the size variable is invalid.

If you do design a class that has invalid states, you should throw an exception when a method can't perform normally because its object is invalid. In the example above, the add() method throws an InvalidObjectException exception if sizeValid is false. This exception clearly indicates to the caller that the add() method did not work and why.

Keeping initialization focused

One final object initialization guideline involves the actual activities of constructors and initializers.

As a C++ programmer, I encountered a class one day that had no methods and no fields -- only a constructor. In trying to determine what this constructor did, I found that it called a function that called a function that called a function, and so on. After looking through several files, I realized that instantiating an object of this class caused a file to be parsed. After that the class was useless.

Granted, this particular class was designed by someone new to object-oriented programming, but its design illustrates an important point relating to constructors and initializers in Java: constructors and initializers should be about initialization.

In a previous section of this article, I recommended that you strive to design objects that give client programmers no way to instantiate the object in an invalid state. Another way to look at this is that client programmers should not have to do anything besides invoke a constructor to get an object in a proper initial state. An object should be fully initialized by the constructors and initializers of its class.

Likewise, just as objects should not be less than initialized after a constructor invocation, they also shouldn't be more than initialized. As a general principle, constructors and initializers should do no more than bring the new object to a proper initial state. To get the object moving after construction has brought it to a proper initial state, you should require that client programmers invoke a method on the object.

Conclusion

Two important design guidelines this article attempts to promote are the "canonical object design" and "omni-valid state principle."

The canonical object design
An object should have state, represented by instance variables that are private. Invoking an object's instance methods should be the only way code defined in other classes can affect the object's state.

The omni-valid state principle
Objects should have a valid state, and experience only valid state transitions, from the beginning of their lifetimes to the end.

With respect to object initialization, this article suggests a number of handy guidelines:

  • The no-way-to-create-an-object-with-an-invalid-state guideline -- Design an object's initializers and constructors such that the object cannot possibly be created in an invalid state.
  • The not-natural rule of thumb -- If an instance variable doesn't have a natural default value, force client programmers to pass an initial value (or data from which an initial value can be calculated) to every constructor in the class.
  • The exceptional rule concerning bad constructor input -- Check for invalid data passed to constructors. On detecting invalid data passed to a constructor, throw an exception.
  • The valid guideline about invalid objects -- Sometimes it will make sense to design a class whose instances can, at times, be in an invalid, unusable state. For such classes, throw an exception when a method can't perform its normal duties because the object's state was invalid when the method was invoked.
  • The hold-your-horses-during-initialization guideline -- Constructors and initializers should be about initialization, the whole initialization, and nothing but initialization.


One last guideline pertains to the nature of guidelines themselves. The guidelines proposed in this column are not proposed as laws you should blindly follow at all times but as rules of thumb you'll probably want to follow much of the time. They are intended to help you acquire a mindset conducive to good design. Thus, the final guideline is:

  • The guideline guideline -- All guidelines proposed by this column should be disregarded some of the time -- including this one.


A request for reader participation

Software design is subjective. Your idea of a well-designed program may be your colleague's maintenance nightmare. In light of this fact, I hope to make this column as interactive as possible.

I encourage your comments, criticisms, suggestions, flames -- all kinds of feedback -- about the material presented in this column. If you disagree with something, or have something to add, please let me know.

Next month

In next month's Design Techniques, I'll continue this mini-series of articles focusing on designing classes and objects. Next month's installment, the second of this series, will offer field and method design guidelines. :END_BODY

ENDNOTE: The small print: "Designing Object Initialization" Article Copyright (c) 1998 Bill Venners. All rights reserved. Traffic Light Applet Copyright (c) 1998 Bill Venners. All rights reserved. :END_ENDNOTE:

  • Print
  • Feedback

Resources
  • For the source code listings that appear in this article, see http://www.javaworld.com/jw-03-1998/techniques/techniques.zip
  • Recommended Books on Java Design http://www.artima.com/designtechniques/booklist.html
  • Object Orientation FAQ http://www.cyberdyne-object-sys.com/oofaq/
  • 7237 Links on Object-Orientation http://www.rhein-neckar.de/~cetus/software.html
  • The Object-Oriented Page http://www.well.com/user/ritchie/oo.html
  • Collection of Information on OO Approach http://arkhp1.kek.jp:80/managers/computing/activities/OO_CollectInfor/OO_CollectInfo.html
  • Design Patterns Home Page http://hillside.net/patterns/patterns.html
  • A Comparison of OOA and OOD Methods http://www.iconcomp.com/papers/comp/comp_1.html
  • Object-Oriented Analysis and Design Methodsa Comparative Review http://wwwis.cs.utwente.nl:8080/dmrg/OODOC/oodoc/oo.html
  • Patterns-Discussion FAQ http://gee.cs.oswego.edu/dl/pd-FAQ/pd-FAQ.html
  • Implementing Basic Design Patterns in Java (Doug Lea) http://g.oswego.edu/dl/pats/ifc.html
  • Patterns in Java AWT http://mordor.cs.hut.fi/tik-76.278/group6/awtpat.html
  • Software Technology's Design Patterns Page http://www.sw-technologies.com/dpattern/
  • Synchronization of Java Threads Using Rendezvous http://www-cad.eecs.berkeley.edu/~jimy/classes/rendezvous/
  • Design PatternsElements of Reusable Object-Oriented Software, In Java http://www.zeh.com/local/jfd/dp/design_patterns.html :END_RESOURCES