Putting an Object in a Safe State

Using UML to Model Classes

As an example, let's consider that we have two ways to construct a database reader class:

Pass the name of the database and position the cursor at the beginning of the database.

Pass the name of the database and the position within the database where we want the cursor to position itself.

Figure 2 shows a class diagram for the DataBaseReader class. Note that the diagram lists two constructors for the class. While the diagram shows the two constructors, without the parameter list there is no way to know which constructor is which. To distinguish the constructors, you can look at the corresponding code listed below.

How the Superclass Is Constructed

When using inheritance, you must know how the parent class is constructed. Remember that when you use inheritance, you are inheriting everything about the parent. Thus, you must become intimately aware of all the parent's data and behavior. The inheritance of an attribute is fairly obvious. However, how a constructor is inherited is not as obvious. After the new keyword is encountered and the object is allocated, the following steps occur (see Figure 4):

The first thing that happens inside the constructor is that the constructor of the class's superclass is called.

Then each class attribute of the object is initialized. These are the attributes that are part of the class definition (instance variables), not the attributes inside the constructor or any other method (local variables). In the DataBaseReader code presented earlier, the integer startposition is an instance variable of the class.

The Design of Constructors

When designing a class, it is good practice to initialize all the attributes. In some languages, the compiler might attempt to do this initialization. As always, don't count on the compiler to initialize attributes! In Java, you cannot use an attribute until it is initialized. If the attribute is first set in the code, make sure that you initialize the attribute to some valid condition, for example, set an integer to zero.

Constructors are used to ensure that the application is in a stable state. For example, initializing an attribute to zero, when it is intended for use as a denominator in a division operation, may lead to an unstable application. You must take into consideration the fact that a division by zero is an illegal operation.

During the design, it is good practice to identify a stable state for all attributes and then initialize them to this stable state in the constructor.

Error Handling

It is rare for a class to be written perfectly the first time. In most, if not all, situations, things will go wrong. Any designer who does not plan for problems is courting danger.

Assuming that your code has the ability to detect and trap an error condition, you can handle the error in several different ways: In a training class based on their book Java Primer Plus, page 223, by Tyma, Torok and Downing, the authors state that there are three basic solutions to handling problems that are detected in a program: fix it, ignore the problem by squelching it, and exit the runtime in some graceful manner. Gilbert and McCarty, in their book Object-Oriented Design in Java, on page 139, basically state the same thing but add the choice of throwing an exception:

Ignore the problem—not a good idea!

Check for potential problems and abort the program when you find a problem.

Check for potential problems, catch the mistake, and attempt to fix the problem.

Throw an exception. (This is the preferred way to handle the situation)

These strategies are discussed in the following sections.

Ignoring the Problem

Simply ignoring a potential problem is a recipe for disaster. And if you are going to ignore the problem, why bother detecting it in the first place? The bottom line is that you should not ignore the problem. The primary directive for all applications is that the application should never crash. If you do not handle your errors, the application will eventually terminate ungracefully or continue in a mode that can be considered an unstable state. In the latter case, you might not even know it for some period of time.

Checking for Problems and Aborting the Application

If you choose to check for potential problems and abort the application when a problem is detected, whenever a problem is detected, the application displays a message saying that you have a problem. Then the code gracefully exits and the user is left staring at the computer screen, shaking his or her head and wondering what bus just happened. While this is a far superior option to ignoring the problem, it is by no means optimal. However, this does allow the system to clean up things and put itself in a more stable state, such as closing files.