|
A Class Design Example
This is a simple single class example that illustrates the design processes involved in creating a single class, from requirements through to implementation. Real-life software development will involve multiple classes, for which one needs also to consider class interaction. However, the basis for OO design is the design of an individual class. For information on multi-class design, see the OOP sections of the Java Guide. This example models a real-life object by a software object. A real-life checking account has identity, state and behavior - just like a software object.
Establishing the RequirementsTo start, we must decide what a checking account object will do and what data it will hold. This process is called 'requirements analysis' and for industrial sized problems is perhaps the most important phase of writing programs. Even for small problems it is important to know exactly what you want before you start writing. For our small problem we will outline what the class looks like before we start coding. The class definition will be a blueprint for checking account objects. The state and behavior that we want for a checking account must be described.
Requirements for StateThree essential data items that will be part of a checking account are :
A real checking account would contain a great deal more information, but for this example, the above is enough. It is too early to think about what types of variables to use for the data. That will come later.
Requirements for BehaviorNext, we need to think about what actions will be performed on a checking account. That is, what behavior will a checking account have? The following three will serve for our purposes:
Requirements for Constructor(s)Also, we need a constructor to create objects from the class, so we must decide what initialization the constructor should do. It seems reasonable that it should create a new checking account and initialize it with the account number, the account holder's name, and starting balance. This single constructor will be sufficient for our purposes.
The Class OutlineNow we have a fair idea about what the class looks like:
For a large project, creating specifications is much more difficult because each object interacts with many other objects.
More RequirementsThe requirements also need to define what each method should do in more detail. The method to accept a deposit will add an amount (in cents) to the current balance. The current balance can be negative or positive. The method to process a check will subtract the amount of the check from the current balance. Overdrafts are allowed, so the balance can become negative. However, if the balance is less than $1000.00 before the check is processed, $0.15 is charged for each check. To simplify the program, we will assume that all input data is correct (so the methods do not need to check for errors.)
Class DesignNow it is time to think about designing our class. The first thing to do is sketch out the class definition.
The 'instance variables' (non-static, class-scope vaiables) is where the declarations of our variables (fields) will go. The table below shows reasonable variable names (identifiers) and data types:
So far, the CheckingAccount class looks like this:
Implementing the ConstructorNext, consider the constructor. The constructor has the same name as the class, and looks like this:
The constructor will be public so that new instances (objects) of the class can be created by other classes. The constructor is used with the new operator to create a new checking account object. It then initializes the account number, the account holder's name and starting balance. Actual parameters are supplied to the constructor when it is used, such as in the following: CheckingAccount bobsAccount = new CheckingAccount( "123", "Bob", 100 ) ; This statement creates a new CheckingAccount object by calling its constructor. The constructor will initialize the object's accountNumber to "123", its accountHolder to "Bob", and its balance to 100. We can complete the parameter list:
Since parameters are used only inside a constructor or method, they often have shorter, less descriptive names than the names given to more permanent data.
Completing the ConstructorSo far, the class looks like this:
Before adding any methods, we can create a tester class and check what we have done so far.
123 Bob 100 Before moving on, consider the expression :
What does this mean? The expression means "the variable accountNumber that is part of the object referred to by account1". The 'dot operator' allows you to access a part of another object (in this case a field, but could also be a method): referenceToAnObject.partOfTheObject Of course, the object has to exist, and there has to be a reference to it. In the program, this happened when the object was constructed: CheckingAccount account1 = new CheckingAccount( "123", "Bob", 100 ); When a method has a reference to an object, the method can:
However, it is common for a class to have private variables and methods. Only methods within the class can access these. Private methods are those that only that class needs to use; often, private methods act as sub-methods to the public methods. Further, classes usually keep their variables private and allow other classes access to them only via public 'getter' and 'setter' methods ('accessor methods'). In this way, these methods can ensure the validity of the values.
Method DefinitionNow that we have a test program, we can add methods to the class one-by-one, testing each method as it is added. Recall the three methods from the requirements analysis:
Remember the syntax for a method definition:
The first line in the above is called the 'header' or 'signature' of a method definition. It does not have to be all on one line. The returnType is the data type that the method returns. It will be one of the primitive data types, or a class. If a method does not need to return anything, we specify a return of 'void' (meaning nothing). The methodName is an identifier chosen by the programmer. It can be any name except for reserved words or identifiers already in use. The parameterList is a list of parameters and their data types. If there are no parameters, the parameter list is omitted, but the two parentheses remain.
The Current Balance methodWhen this method is called, it will not alter any data in a checking account object, but merely return the current balance. The return type will be the data type of the account balance - int. No information is required by the method so there are no parameters. So the signature (header) for this method will be: public int currentBalance() Other names for the method would also work, of course.
Implementing the Current Balance MethodThe method returns the current balance, which is an int so the return type is int. The method does not need any input data, so its parameter list is empty. You still need to have the two parentheses ( ) even though there is nothing inside of them.
With the currentBalance() method added, the program can be tested again. It is a good idea to alternate between writing and testing like this. By testing each part as it is added you can catch problems early on, before they can cause further (possibly 'mysterious') problems. Of course, it is still essential to have a good initial design. This is how houses are built, after all: starting with a good design, the foundation is laid, and structures are built and tested until the house is finished. Here is the class now:
And here is a new version of our tester class. This time it creates two accounts.
The output of this test program will be : 123 : Bob : 10092a-44-33 : Kathy : 0
Method to Accept a DepositNext we will implement the method that accepts a deposit. Deposits will be expressed in cents. The method will have one parameter, the number of cents to be added to the balance. The method will not return a value, so its return type will be void. And because the return type of the method is void, no return statement is needed. When the method is run, it will automatically return to the place where it was called from after the last statement of the method is executed.
Method to Process ChecksThis method, to process a check, is slightly more complicated:
The method can be implemented like this:
The complete source code for classes CheckingAccount and CheckingAccountTester are available on the 'Resources' page of the TJI web site as a complete project that you can download and play with.
'Scaffolding'If this class were to be used in an actual bank, it would need a great deal more testing! Usually testing involves placing statements that write out information to the terminal as the program is being run. Thoughtful placement of these statements can greatly ease software development. These statements (and other statements intended to be used for testing and program development) are sometimes called 'scaffolding'. Scaffolding statements are put in place as the program is being written and tested, and are removed when testing is finished. This is similar to the scaffolding used when a building is being constructed. Well-placed scaffolding greatly aids the carpenters, roofers, painters and other trades that work on the building. Programmers should do the same for themselves. It would be useful to have a simple way to print out the current state of a CheckingAccount object. Class 'Object' - which is at the root of the class hierarchy in Java - has a toString() method that serves this purpose, and most classes in Java will re-implement ('override') this method to produce a meaningful output for that particular class. We can do the same for our class CheckingAccount. The statements that we previously wrote in the calling tester class to output the state of a CheckingAccount object were somewhat awkward: System.out.println(account1.accountNumber + " : " +account1.accountHolder + " : " + account1.currentBalance() ); Creating a toString() method inside CheckingAccount will simplify this greatly. By moving the display code into class CheckingAccount itself we need only write: System.out.println(account1.toString()); The toString() method would be:
Finally ...Now let's try to do something a little more tricky. Suppose there are two accounts, Bob and Kate. Let's say that Kate writes a $300 check to Bob, and that Bob deposits this check in his account. Here are the statements we can write to test this. int check = 30000;katesAccount.processCheck(check);BobsAccount.processDeposit(check);KatesAccount.display();BobsAccount.display();
|