Now that we've distributed the 'knowing responsibilities' to the various classes, it's time to distribute the 'doing responsibilities'. But how do we do this?
What we need to to do is to look at each of the tasks that the program must do and simulate in our head how this will happen. By simulating, we'll identify a set of smaller tasks that make up each task. We then need to decide which classes will get which of the smaller tasks. In the implementation stage, these small tasks will become methods.
Our fourth principle of design becomes:
4. Mentally simulate each task. Identify sub-tasks. Assign sub-tasks to classes.
In this way, we break down our task into the small units. How small? Well, small enough so that when we have sub-tasks that are common to more than one task we can write one method which both of the tasks can use. This gives us
So our fifth principle of design becomes:
5. Don't write code that does the same thing in two different places.
Let's look at how these principles get applied in practice.
So let's start our mental simulation. We have a person sitting at a desk. There is a line of students. Each student wants to either register, see their personal information if they have already registered, drop or add courses, change their personal information, or pay their fees.
Therefore, one of the things that we need to do is to allow the user to select from a number of possible actions, such as
and then execute the appropriate code to accomplish the task.
Now in order to do this we need to
So these are our sub-tasks, and now we have to assign our sub-tasks to appropriate classes. Since we've already decided that all I/O will happen in our Input/Output class, we clearly need to put the first two 'doing resposibilities' in the Input/output class, which will then look like:
Input/Output class | |||||||||||||||||||||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||||||||||||||||||||
stuff associated with file that we'll write to / read from |
|
||||||||||||||||||||||||||
input user choice | |||||||||||||||||||||||||||
Our third sub-task -- 'execute appropriate code for the task' clearly needs to be broken down further into sub-tasks, which is what we'll do next.
REGISTER A STUDENT
First of all, let's look at 'register a student'. How does this break down into sub-tasks? Let's simulate again. A student is at the registration desk, and wants to register. We need to
Once again, it's clear that the first two sub-tasks belong in the Input/Output class, which will then look like:
Input/Output class | |||||||||||||||||||||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||||||||||||||||||||
stuff associated with file that we'll write to / read from |
|
||||||||||||||||||||||||||
input user menu choice | |||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||
input student's course choice | |||||||||||||||||||||||||||
input student's section choice |
Now we need to break down 'store student's personal information' into sub-tasks. We'll need to:
Now how will we allocate these tasks to our classes? Well, we know that we can do the first two tasks by creating a constructor for the Student class which has the student's personal information as parameters. How will we do the next two tasks?
Well, what we need to do is to get the reference to the newly created student object, and append it to our list of students which is in the class StudentList. A couple of ways of doing this come to mind:
1) Call the student constructor. From inside the student constructor, call a method in StudentList which takes a parameter of type Student and appends the reference to the newly created Student object to the list. (we will have to use this, which is the variable which contains the reference to the newly created Student object in the Student constructor
2) Call a method in StudentList which calls the Student constructor. The Student constructor will then return the reference to the newly created object, and it can be appended to the list.
There are pros and cons to both these; I suggest we choose 2 for now since it is the construction with which we are probably most familiar.
So if we choose option 2, StudentList will need to have a method which takes all the personal information as parameters, calls the Student constructor, and appends the new Student reference to the list, and increments the number of students variable. And, of course, we'll need to add a constructor to Student which takes the student's personal information as parameters. These classes will then look like this:
Student class | |
DATA (KNOWING) | METHODS (DOING) |
name | Constructor (name, st. num., dob, addr.) |
st num. | |
dob | |
fees owing | |
address | |
set of courses | |
number of courses | |
set of sections | |
StudentList class | |||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||
set of students enrolled in university |
|
||||||||
number of students enrolled in university |
Now we need to ask the student which courses she wants to take, and which sections she would like to be in. If we break this down into sub-tasks, clearly if we make a design for a student to select one course, and one section within that course, we can then make a loop of some kind and allow the student to choose multiple courses and sections. So we'll begin by designing for a single course and section.
Let's say that the student should know the name of the course (for example, she has a course calendar) but not necessarily which sections exist and what time slots the lectures for a particular section are. Simulating this gives us:
Now we need to distribute these tasks to the various classes.
Clearly 1. and 2. belong in the Input/Output class, but what about #3? What do we have to do to check that a course actually exists, given that the student will be giving us its name (i.e. a String)? Well, since CourseList is the class that contains references to all the courses, we'll have to do something in this class, and since Course is the class that stores the name for each course, we'll have to do something in this class.
More specifically, for each course in the list in CourseList we'll have to use a method in Course to determine whether it has the same name as the one specified by the student. This suggests that the method in CourseList will loop over the courses, and the method in Course will return a boolean if the name the student has given is the same as the name of the course object.
So our design now looks like this:
Input/Output class | |||||||||||||||||||||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||||||||||||||||||||
stuff associated with file that we'll write to / read from |
|
||||||||||||||||||||||||||
input user menu choice | |||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||
input student's course choice (prompt, get input) | |||||||||||||||||||||||||||
if invalid course, print error message, try again | |||||||||||||||||||||||||||
display section information | |||||||||||||||||||||||||||
input student's section choice | |||||||||||||||||||||||||||
if invalid section, print error message, try again |
CourseList class | |||||||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||||||
set of courses offered by university |
|
||||||||||||
number of courses offered by university |
Course class | |||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||
course name |
|
||||||||
set of sections for this course | |||||||||
number of sections for this course |
So now we can decide whether a course exists. Clearly, #4. (print error message, try again) should also be in the Input/Output class. What about #5: Display sections that aren't full? How should we do that?
Well, since the course 'knows' about its sections (i.e. section is a member of course) we will have to find the course. Again. Isn't this a little wasteful? We just searched through all the courses to determine whether it exists, and now we're going to have to search again? (remember, we have the String name of a course, but to run any methods, we need the reference to the course object). Well, this is a somewhat controversial question; as usual there's more than one way to handle this situation, and different people have different preferences.
Let's first talk about the different ways to handle this. First of all we could leave the Does course exist? methods as they are, and write new methods that search through the array of course references in the same manner, and return a reference to the course, rather than a boolean. With the course reference in hand, and we can call a method in the Course class to get the section information that we want.
The downside of this approach is that we'll be violating one of our guidelines:
5. Don't write code that does the same thing in two different places.
Since we're searching through the list of courses in both the method that returns a boolean and the method that returns a course reference, we'll be writing the same or very similar code in two different places.
Another possibility is to try to write the code once. In this scenario, we could modify the Does course exist? code so that search through the list of courses and instead of returning a boolean, we return a reference to course. In the case that the course doesn't exist, our method could return the null reference, and otherwise it could return a reference to the course in question. We could then test the returned course reference, and if null print the error message and ask the student to select another course, and if non-null, we now have the reference to the course in hand, and we can call a method in the Course class to get the section information that we want.
What's wrong with this approach? Well, it violates the sixth design principle, which is:
6. A method should accomplish a single task.
In this case, we have a method which does two things: it tells us whether a course exists, and it gives us the reference to the course if it does exist.
So how should we proceed? Each way violates one of our design principles. There are times when we can't easily satisfy all our design principles simulataneously, and at these times its good to remember that our design principles are really just guidelines, and that we will have to make some sort of compromise between them. The choice is quite subjective, and I would recommend the second solution. In my opinion, it's better to have the method doing two things than it is to have the same code in different places.
So the modified CourseList will now look like this:
CourseList class | |||||||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||||||
set of courses offered by university |
|
||||||||||||
number of courses offered by university |
Ahh, you say, this is well and good, but how do we return information about section availability? Well, we have the reference to the course in hand, so if there was an appropriate method in Course we could get the number of sections in the course. Once we have the number of sections, we could loop this many times, and each time through the loop call methods in Course which return the required information about the section in question. These methods in Course would have to take a section number as a parameter and use it to access information about each section. Since sections themselves are objects, we'll also need some methods in Section to help us out.
Yikes! This is getting complicated. Let's take it step by step. What information do we need about the sections? We need to know a) whether there are spaces (or perhaps the number of spaces) b) the time of the lectures, and c) the room for the lectures. Therefore we'll need three methods in Section: one which returns the number of spaces, and one which returns the lecture times, and one which returns the lecture room. The first is easy: we just subtract the number of students in the section from the capacity for the section, and.... hang on.... checking our design so far, we don't have a variable which stores the capacity of a section -- a section doesn't know what its capacity is! Well, let's add this to the knowing responsibilities of a section. Then the first method will return the difference of these two values (if it is positive) or zero (if it is negative).
The second method will return the lecture times, and the third will return the lecture room. The Section class will then look like:
Section class | |
DATA (KNOWING) | METHODS (DOING) |
set of students in section | get Available spots: cap. - num students |
number of students in section | get lecture times |
capacity of section | get lecture room |
instructor name for section | |
lecture room | |
lecture times |
And how do we have to change Course?
Course class | |||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||
course name |
|
||||||||
set of sections for this course |
|
||||||||
number of sections for this course |
|
||||||||
|
So know we have all the appropriate methods to have the student select a course, decide whether the course exists, and if it does display information about the sections so that the student can make a choice of section. Clearly we'll want to display this information using a method in the Input/Output class, and then get the student's choice of section using a method in the same class. Given the choice of section (an integer, say) we still need to assign the responsibilities to
The first item is probably best assigned to the Input/Output class (determining whether input is valid is related to this class), the second item again belongs to the I/O class, and the third item will need to be broken down into its sub-tasks.
To assign a student to a section, we need to add the student to the set of students in the section and increment the number of students in the section by one. This can be done by a method in the Section class: it will take a reference to Student as a parameter, and since we're going to use arrays to store sets of things, it will store this reference in the array, and increment the number of students variable. To assign a section to a student, the same kind of thing will take place: a method in Student will take a reference to Section as a parameter, and store this reference in an array, incrementing the number of courses variable. Whoops! Almost forgot. We don't have a reference to Section, we just have an integer number of the section. Therefore we'll need to have a method in Course which takes the integer Section number and returns the reference to the Section. Once we have this reference, we can then proceed as above.
We also need to do a similar thing for the Course reference -- store it in the set of courses of the Student object. Since we already have the Course reference in hand, all we have to do is to have an appropriate method in the Student class which takes a reference to Course as a parameter, and stores the reference in the array of courses that the student is taking.
Thus our updated design for these classes will look like:
Section class | |||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||
set of students in section | get Available spots: cap. - num students | ||||||||
number of students in section | get lecture times | ||||||||
capacity of section | get lecture room | ||||||||
instructor name for section |
|
||||||||
lecture room | |||||||||
lecture times |
Course class | |||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||
course name |
|
||||||||
set of sections for this course |
|
||||||||
number of sections for this course |
|
||||||||
|
|||||||||
|
|||||||||
Student class | |||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||
name | Constructor (name, st. num., dob, addr.) | ||||||||
st num. |
|
||||||||
dob |
|
||||||||
fees owing | |||||||||
address | |||||||||
set of courses | |||||||||
number of courses | |||||||||
set of sections | |||||||||
Whew. This now should be enough to register a student. Now we can move on to the next item on our menu: pay fees.
DISPLAY INFORMATION
What we'd like to do here is to:
Clearly the first two items belong in the I/O class. The third item belongs in the StudentList class since this is the only class that 'knows' about all the students, and the fourth item belongs in the I/O class again.
Now this situation is very similar to the situation when we wanted to search for a course name. We used the CourseList class to get a reference to each of the courses, and then used a method in the Course class in order to determine whether the names were the same. In this situation, we'll need to determine whether the student numbers are the same, and when we find the right student number, we'll return a reference to the student. If we don't find the student number, we'll return the null reference.
Once we have the reference to the correct Student object, we'll need to call a number of methods in Student which just return the information that we want, and then we'll display it.
Our design now will look like
StudentList class | |||||||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||||||
set of students enrolled in university |
|
||||||||||||
number of students enrolled in university |
|
Student class | |||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||
Constructor (name, st. num., dob, addr.) | |||||||||
|
|||||||||
name | set/get name | ||||||||
st. num. | set/get st. num | ||||||||
dob | set/get dob | ||||||||
fees owing | |||||||||
address | |||||||||
set of courses |
|
||||||||
number of courses | |||||||||
set of sections |
|
CHANGE ADDRESS, PAY FEES
Let's look at these two tasks. They are fairly simple -- for the first we need to
The first three items are already in our design! Hallelujah. Items 4 and 5 clearly belong in the I/O class, and item 6 can be accomplished by writing a 'set' method in the Student class which takes a reference to String as a parameter, and sets the address instance variable.
Paying fees is identical, except that the method in Student to pay fees will have a parameter of type double, and will subtract its argument from the fees owing instance variable. In a real setting, we might check to make sure that the student isn't paying too much, but we won't worry about that here.
Our design becomes:
Student class | |||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||
Constructor (name, st. num., dob, addr.) | |||||||||
|
|||||||||
name | set/get name | ||||||||
st. num. | set/get st. num | ||||||||
dob | set/get dob | ||||||||
fees owing |
|
||||||||
address | get/set address | ||||||||
set of courses |
|
||||||||
number of courses | |||||||||
set of sections |
|
Input/Output class | |||||||||||||||||||||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||||||||||||||||||||
stuff associated with file that we'll write to / read from |
|
||||||||||||||||||||||||||
input user menu choice | |||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||
if invalid course, print error message, try again | |||||||||||||||||||||||||||
display section information | |||||||||||||||||||||||||||
if invalid section, print error message, try again | |||||||||||||||||||||||||||
if invalid st. num., print error message, try again |
DISPLAYING ADMINISTRATIVE INFORMATION
We want to display:
The first one is quite easy:
A similar approach will work for getting student numbers. Let's assign these sub-tasks now. The first we can give to StudentList: a method to return the total number of enrolled students. The second requires a loop somewhere - wherever we're executing this. (perhaps in I/O? or in main?) The third requires another method in StudentList which has a parameter of type int and returns a reference to Student. The fourth requires... hang on... I think we already have a method that returns student name. Good. And finally we need a method in I/O to display the name.
For student numbers, we'll additionally need a method in Student which gets the student number... Good, we already have one. So all we need is a method to display the student number.
So that takes care of displaying a list of all student names and numbers at the university. See how things get easier? Once we've designed many of the lower level methods, we can do more complex things by calling them.
How about displaying the list of course names? This will be very similar to the list of students, and I'll leave this design to you. In fact all of the rest of these are quite similar, so I'll leave these as an exercise.
StudentList class | |||||||||||||
DATA (KNOWING) | METHODS (DOING) | ||||||||||||
set of students enrolled in university |
|
||||||||||||
number of students enrolled in university |
|
||||||||||||
|
|||||||||||||
get number of students |