Course Links

Exercises

Resources

External

Overview

Writing programs with classes requires a bit of a shift from previous programming you may have done. Instead of picturing a program as a series of steps that must be accomplished, you need to think of it more as a series of interactions (between objects). Each interaction may be simple in its own right, but enough simple interactions together can amount to complicated behavior. Nevertheless, when designing a class, you must force yourself to think momentarily only of the particular aspects of the problem you are trying to model in that class. If you do that well, then the larger issues will work out in the interaction between the classes.

A Practical Approach

The first step in writing a class is to identify the concept that holds it together. Does your class model a thing? A concept? A task or group of related tasks? Each of these may serve as the focus for a class. Classes may also serve more utilitarian purposes, such as wrapper classes whose sole reason for existence is to instantiate and run an instance of some more substantial class.

Once you have the concept, make a list of the information the class will need to keep track of in order to do what it needs to. These will form the class fields. Note that some of these pieces of information may themselves be instances of classes. String is a common field type, but you may use anything from the various Java packages, or a class you have defined yourself. Most information will need to be kept track of individually for each instance of a class; these will be the instance fields. Keep in mind, though, that occasionally there may be information that only needs to be recorded once for the whole set of class instances, no matter how many there are. These will be the class fields, and will be indicated as such with the static keyword.

Now make a list of the things the class will need to do. These will form the class methods. For most classes, in addition to any specialized methods that may be desired, you will usually want to provide a set of accessor and mutator (or manipulator) methods to allow read and write access to the class's private data fields.

Finally, think about how you will be creating instances of this class. What information will you be providing? This will help you determine what constructors you need to write. The job of the constructor is to initialize the object's fields. If you can get by with default values in every case, then you can make do with a single constructor with no arguments. (In the rare case where no initialization is necessary at all, you need not define a contructor.) On the other hand, if you want to be able to create class instances that differ from one another, then you will need some additional constructors that take arguments.

Once you've filled out the class concept as described above, you can begin to write code. Create a new file with the same name as your class, and put the class definition inside it. List fields first (static first, with public before private), then constructors, then methods. Finally, once you have the structure of the class with all the elements in place you can write the constructor and method bodies.

An Example

Let's take as an example a new class, called CreditCard, that will keep track of simple credit card accounts. For the sake of this example, let's assume that cash advances and all the other variations don't exist, and that a credit card is only used to purchase merchandise. Spend a moment reflecting on what data need to be associated with a credit card, and what operations can be performed with (or on) it.

Clearly, the card will need to keep track of the balanceOwed. Most cards also have a creditLimit, to prevent people from charging more than they can ever pay back. It would also be convenient if the card keeps track of its own interestRate, since cards often have different rates. We could also add more data fields to keep track of the card number, expiration date, over-limit fees, payment due-dates, late-payment fees and so forth, but let's ignore those for now to keep things simple.

So far, we've listed fields that will be needed for individual CreditCard objects. That may be enough for many classes, but let's suppose that our program will manage several credit card accounts. We may therefore wish to keep track of the totalCreditLimit and totalBalanceOwed for all cards. Because this is information about the class as a whole rather than a specific card, these should be implemented as class fields (i.e., declared static). With all these fields in place, our java file should look something like this.

Once we've decided on the data fields, we can think about constructors. Under what circumstances will we want to create a CreditCard object? Probably, only when opening a new account. Therefore we should write a constructor that will take arguments for the credit limit and interest rate, and set the balance to $0.

Next we need to think about what sort of accessors and manipulators to create. Let's start with accessors first. Thinking about how credit cards work, we may decide that we want to make all the data fields in the class freely visible, so we'll include a full set of accessors that return their values. Next we turn to setting values. It's probably safe to add the simple manipulators setCreditLimit() and setInterestRate(). However, we want to control the balance more tightly (to prevent fraud), so we won't include setBalanceOwed(). Our growing file now looks like this.

In terms of operations, the card is used to make purchases, the cost of which must later be paid back. Periodically, interest must be paid if the balance has not been paid off. Each of these actions can be turned into a method: makePurchase(), makePayment(), chargeInterest(). One other action whose need may not be initially obvious is closeAccount(); recall that we are keeping track of the total credit available, so we will need this. (Other methods may be appropriate depending on how we envision the class being used. For example, if customers will want to check whether a purchase is allowed, we may want a method creditAvailable to return the difference between the current balance and the credit limit. For now, let's stick with the four mentioned above.) A final handy method would show us the details of the account (interest rate, credit limit, and balance remaining). We can call this one print().

For each method, we'll need to decide the call signature. (In other words, we'll need to decide on the list of parameters and the return type, if any. Many of the methods won't need any formal parameters, because they always have access to the fields of the CreditCard object for which the method was called.) So, for example, chargeInterest() won't need any arguments and doesn't need to return anything, since it works entirely within the class itself:

public void chargeInterest();

On the other hand, purchase() needs to be given the price of the goods purchased. It also needs a way to tell the caller whether the purchase failed due to insufficient funds available, so it must return a bool value:

public bool makePurchase(float);

The complete framework should be filled out by continuing these types of decisions. Add comments as you go. Your working file should look like this.

Implementing the Methods and Testing

Now we've got a skeleton file, which lists the fields and methods methods.

The methods should be filled out by writing their implementations one at a time. Try it yourself, then look here. Many people prefer to write the method bodies as they go, rather than filling them in at the end. This is fine, so long as you have spent time thinking through the methods you will need. If you don't, you may end up spending a lot of time implementing a method, only to decide in the end that it needs to work differently than you had originally thought! Modifying code because of a failure to plan ahead wastes time and is a good way to introduce bugs.

The final step in finishing your class is to write a wrapper class that will try out all the methods you have just written. This need not be very complicated; it just needs to call each method in turn, and include enough printouts that you can ensure that the results are correct. As a general rule, at minimum you should produce enough test cases so that every line of code is run at least once. Here's a sample; you could certainly test the class more thoroughly than this if you wished.

If our class is supposed to work as part of a larger program, then good testing is the key to success. If it works well on a thorough set of test cases, then it should also work just as well when combined with other classes. In theory, if we've done your job well at each stage, then the final step of combining your classes into a working whole should be a piece of cake.