next up previous
Next: Design Principles Summary Up: Design - How It Previous: Who Knows What?

Who Does What?

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

  1. economy, since we will write less code
  2. verifiability, since verifying the correctness of the common sub-task helps verify the correctness of all the tasks that use it,
  3. maintainability, since if we find an error or decide to change the behaviour of the task, we might need to only change code in the one sub-task

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

  1. register a student
  2. display information
  3. drop/add courses
  4. pay fees
  5. change address

and then execute the appropriate code to accomplish the task.

Now in order to do this we need to

  1. display the choices for the user
  2. input the choice the user makes
  3. execute appropriate code for the task

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
Display Menu  
  register student
  display student information (given st. number)
  pay fees
  change address
  add/drop course
  select section
  print admin. information
  quit
  Admin. Information printing:
  st. names, nums
  course names, enrollment
  instructors
  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

  1. Get the student's personal information
  2. Get the student's choice of courses/ sections
  3. store the personal information (student object)
  4. register the student in a course/section

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
Display Menu  
  register student
  display st. info (given st. num)
  pay fees
  change address
  add/drop course
  select section
  print admin. information
  quit
  admin information printing:
  st. names, nums
  course names, enrollment
  instructors
  input user menu choice
Input:  
  student name
  student number
  student address
  student date of birth
  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:

  1. create a new student object
  2. fill in the appropriate fields in the object
  3. append the reference to the student object to the StudentList
  4. increment the number of students variable

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
create new student:  
  call Student constructor
  put reference in list
  increment number of students
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:

  1. Ask student which course she wants to take
  2. Input choice
  3. Check to see that course actually exists
  4. If it doesn't, print error message, try again
  5. If it does, display sections that aren't full so that student can choose one
  6. Get student's section choice
  7. Make sure that section exists
  8. Add course and section information to student's information
  9. Add student to section; increment number of students in section

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
Display Menu  
  register student
  display st. info (given st. num)
  pay fees
  change address
  add/drop course
  select section
  print admin. information
  quit
  admin information printing:
  st. names, nums
  course names, enrollment
  instructors
  input user menu choice
Input:  
  student name
  student number
  student address
  student date of birth
  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
Does course exist?  
  (parameter: String coursename. returns: boolean)
  Loop over course references
  call method in course (parameter: String coursename. returns: boolean)
  if exists, return true immediately
  if doesn't exist for all courses, return false
number of courses offered by university  

Course class
DATA (KNOWING) METHODS (DOING)
course name
Does course exist?  
  (parameter: String. returns: boolean
  perhaps define as equals method
  should compare argument to name of object
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
Does course exist?  
  (parameter: String coursename. returns: Course ref.)
  Loop over course references
  call method in course (parameter: String coursename. returns: boolean)
  if exists, return ref. immediately
  if doesn't exist for all courses, return null
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
Does course exist?  
  (parameter: String. returns: boolean
  perhaps define as equals method
  should compare argument to name of object
set of sections for this course
get available spots in section  
  (parameter: integer (section number), returns: integer)
  call available spots method for appropriate section
number of sections for this course
get lecture times in section  
  (parameter: integer (section number), returns: String)
  call get lect. times method for appropriate section
 
get lecture room for section  
  (parameter: integer (section number), returns: String)
  call get lect. room method for appropriate section

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

  1. make sure that the section exists (i.e. the student has chosen one of the displayed sections)
  2. if it doesn't exist, display an error message, try again
  3. if it does exist, assign the student to the section, assign the section to the student

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
add student  
  (parameter: Student ref., returns: void)
  put ref. in array
  incr. num students
lecture room  
lecture times  

Course class
DATA (KNOWING) METHODS (DOING)
course name
Does course exist?  
  (parameter: String. returns: boolean
  perhaps define as equals method
  should compare argument to name of object
set of sections for this course
get available spots in section  
  (parameter: integer (section number), returns: integer)
  call available spots method for appropriate section
number of sections for this course
get lecture times in section  
  (parameter: integer (section number), returns: String)
  call get lect. times method for appropriate section
 
get lecture room for section  
  (parameter: integer (section number), returns: String)
  call get lect. room method for appropriate section
 
get Section ref.  
  (parameter: integer, returns: Section ref.)
  return ref. to section

 

Student class
DATA (KNOWING) METHODS (DOING)
name Constructor (name, st. num., dob, addr.)
st num.
add section  
  (parameter: Section ref., returns: void)
  put ref. in array
  incr. num courses
dob
add course  
  (parameter: Course ref., returns: void)
  put ref. in array
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:

  1. prompt for student number
  2. input student number
  3. search through our list of students until we find that number
  4. display all the information about that student that we've stored.

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
create new student:  
  call Student constructor
  put reference in list
  increment number of students
number of students enrolled in university
find student:  
  parameter: integer student numbe. returns: Student object
  Loop over set of students
  call boolean method in Student: true if right st. number
  if st. num. found, return ref. to Student
  if st. num not found, return null

Student class
DATA (KNOWING) METHODS (DOING)
  Constructor (name, st. num., dob, addr.)
 
equals method  
  parameter: int st. num. returns: boolean
  test for equality of argument and st. num.
name set/get name
st. num. set/get st. num
dob set/get dob
fees owing  
address  
set of courses
add course  
  (parameter: Course ref., returns: void)
  put ref. in set of courses
number of courses  
set of sections
add section  
  (parameter: Section ref., returns: void)
  put ref. in set of sections
  incr. num courses

CHANGE ADDRESS, PAY FEES

Let's look at these two tasks. They are fairly simple -- for the first we need to

  1. prompt for student number
  2. get st. number
  3. get ref. to Student object
  4. prompt for new address
  5. input new address
  6. set new address in Student object

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.)
 
equals method  
  parameter: int st. num. returns: boolean
  test for equality of argument and st. num.
name set/get name
st. num. set/get st. num
dob set/get dob
fees owing
pay fees  
  parameter: double amount returns: void
  subtract amount from fees owing
address get/set address
set of courses
add course  
  (parameter: Course ref., returns: void)
  put ref. in set of courses
number of courses  
set of sections
add section  
  (parameter: Section ref., returns: void)
  put ref. in set of sections
  incr. num courses

Input/Output class
DATA (KNOWING) METHODS (DOING)
stuff associated with file that we'll write to / read from
Display Menu  
  register student
  display st. info (given st. num)
  pay fees
  change address
  add/drop course
  select section
  print admin. information
  quit
  admin information printing:
  st. names, nums
  course names, enrollment
  instructors
  input user menu choice
Prompt for:  
  student name
  student number
  student address
  student date of birth
  fees to pay
  course to take
  section selection
 
Input:  
  student name
  student number
  student address
  student date of birth
  course choice
  section 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:

  1. a list of all student names and numbers in our university
  2. a list of all course names
  3. given a course name, display name, st. num., sec. num., for all students in course
  4. given course name, section number, display all student names, nums in section
  5. total amount owing the university, and average amount per student

The first one is quite easy:

  1. get total number enrolled students
  2. loop while less than than number
  3. call a method in StudentList which has integer parameter and returns corresponding ref. to Student object.
  4. using the ref. to Student call method in Student which gets student name
  5. output student name

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
create new student:  
  call Student constructor
  put reference in list
  increment number of students
number of students enrolled in university
find student:  
  parameter: integer student numbe. returns: Student object
  Loop over set of students
  call boolean method in Student: true if right st. number
  if st. num. found, return ref. to Student
  if st. num not found, return null
 
get Student object:  
  parameter: int. returns: ref. to Student object
  returns corresponding ref. from set of refs.
  get number of students


next up previous
Next: Design Principles Summary Up: Design - How It Previous: Who Knows What?
Chris Trendall
Copyright ©Chris Trendall, 2001. All rights reserved.

2001-12-09