This was an essay I wrote to my students near the beginning of my CS2 course to help them get their heads around designing classes and writing methods.
This is the second semester of Java programming. The first semester was about introducing you to the “tools” of programming: Java keywords, variables, loops, arrays, methods, and so forth. The second semester advances your knowledge by taking the tools you learned about and applying them in new ways so that your programming skills can be further refined. It may come as a surprise to you, then, than what you’re encountering now in some fundamental ways from what you learned before.
In a typical first-semester programming course that uses Java, methods are introduced as a way to do a computation. For example, “convert Fahrenheit to Celsius” or “compute the interest on a loan.” You might have done all the input, computations, and output in a single method — most likely the main() one. In such a setup, the class exists simply as a wrapper for the method and is otherwise superfluous. It provides a home for the computation.
At some later point you were taught about creating other methods, but perhaps these were introduced as helpers for the main method. If the main method got too long, for example, you were encouraged to write these helpers to break down the computation into smaller pieces. Or you were told not to write a piece of code twice; instead, put it in a method and call it. That’s good advice, but it doesn’t paint the whole picture.
It’s time to turn some of these concepts inside-out and learn what classes are really for.
A Class Encapsulates Data
A class exists to encapsulate (hold) data. These data are held in the instance variables, or fields, of the class. The class defines what the data is through the names and types of the instance variables.
An object, as you may know, is an instantiation of the class. Where the class just defines what the variables are, the object holds the actual values. The values of these variables at any given moment in time represents the state of the object at that moment.
I just wrote a lot of deep stuff there. Go back and re-read it. Let in sink it. It may conflict with what you think classes and objects are for; that’s my goal.
Note that I haven’t yet talked about methods. They’re not important now. We’ll get to those in a moment. For now, we are only discussing data.
State Changes Over Time
The state of an object (that is, the data it encapsulates) changes over the life of the object. The object is created and moves from state to state by having the values of its fields mutated.
Consider a class that represent a bank account:
public class Account
private int acctNum;
private String owner;
private double balance;
An object is instantiated with some initial values:
Account a = new Account(100, “J Smith”, 50.00);
The state of the object at this moment is the value of the three fields:
(100, “J Smith”, 50.00)
At some point in the future, the balance on the account changes:
(100, “J Smith”, 70.00)
And then changes again:
(100, “J Smith”, 40.00)
Perhaps the owner goes through a life-altering event and needs to change their name. This, too, is reflected as a new state:
(100, “J Lee”, 40.00)
And then maybe that’s the end of it. The object is destroyed and the data erased. This object had four distinct states over its life. At any moment we could inspect the object and say “this is what the bank account looks like right now.”
When we define a class, we must first analyze what data we need to store. That is because classes exist to store data. We should choose only the data that is minimally necessary to represent the state that we need. For our purposes, it is sufficient to store three pieces of information about a bank account. In a different situation, we might need more, or less, or a different set of fields.
Before you write any methods, before you even think about computations, you must figure out what the data is supposed to be. That will drive the whole programming process.
Methods Mutate State
Now we’ll talk about methods.
Methods exist to manipulate data — to change it or to move it around. Most of the methods you write modify the state of an object; that is, they alter the values stored in the instance variables.
Methods should be defined in terms of behaviors: what they do, not how they do it. Look at the state changes from the data analysis phase and figure out what methods are needed to make those state changes happen. It helps to use active verbs that are divorced from the actual underlying implementation. Remember, the user can’t see the fields because they are private. Ideally, the method names should make no reference to the field names, although this is not always possible in practice.
From the state changes shown above, three possible methods jump out immediately:
These are the three actions we can perform on the account to move it from state to state: we can deposit some money, we can withdraw some money, and we can change the owner’s name. At minimum, we need these three methods to be able to implement the state changes in our use case.
Once we’ve identified the methods we need, only then would we start writing code. It is a mistake, which many beginners make, to start writing code and then figure out what it’s supposed to do. You are no longer beginners. Figure out what it’s supposed to do, then write code.
This process is called “data-driven design.” Start by figuring out what data you want to store. Then design the methods around them.
Draw pictures. You’ll see me draw lots of pictures in class. They are part of my mental design process. They should be part of yours, too. I see the data moving around. I see where it comes from and where it goes. I see it mutating. Then I write code to make it happen.
Write psuedo-code. Pseudo-code is half-English (or the language of your choice), half-code. It’s a quick jotting down of the steps that the code will need to perform in order to carry out the implementation. I usually write psuedo-code on paper or in comments. It’s my to-do list to make sure I didn’t forget any steps.
Thoughts on the BetterArray
(Note: the first assignment in this course was to implement our own version of Java’s ArrayList, which we called BetterArray.)
During lecture we went through the data-driven design process with the BetterArray. We started with the data (an array of strings and a counter) and then wrote down the methods we needed.
We drew pictures. We wrote pseudo-code. We wrote comments and to-do lists.
Only then did we write actual Java code. And we tested it thoroughly to make sure it conformed to our desired behavior.
This project is nothing more than array manipulation, for which you often need loops. Working with arrays usually goes hand-in-hand with loops. It will help tremendously if you draw “before” and “after” pictures of what the array should look like. Draw arrows showing how the strings move around. Develop a “movie” in your mind so you can see the array transforming itself from one state to the next.
You will probably need to work several examples for each method so you can get a clear grasp on the starting and ending points of each loop. For example, since you can insert at the beginning, middle, and end of the array, make sure your loop works for all three cases. Draw a picture of each of the three cases showing the before and after state.
I won’t claim that this stuff is easy. If programming was easy, everyone would be doing it. It’s hard, but it is possible. Learn to resist jumping in to start coding and take some time to analyze. You will be rewarded.