Class Dependency Diagram

In this class we are considering far larger programs than we dealt with in IC210 ... and we are interested in being able to handle work on far larger programs than our IC211 projects! Therefore, it's important that we be able to understand, reason about and make design decisions about large projects based on a higher-level view than we get from simply mucking about in all the source code. One way of getting a big picture view is to look at the Class Dependency Diagram.

Class Dependency Diagram
An oval/box represents a class. There is an arrow from class B to class A if
  • class B calls a method of class A
  • class B accesses a field of class A
  • class B extends class A
  • class B contains a reference to class A
To the right is an example of a Class Dependency Diagram for a solution to Proj01. Here is a gallery of the diagrams for all of you Proj01 solutions: 3rd period section, 5th period section,

ACTIVITY
worksheet

Doing the activity should leave you with a diagram that is augmented with the names of methods/fields that explain the edges (as shown below). Hopefully in the process you recognized that this diagram is a high-level view of the program (meaning showing the structure without all the details). In the future, when we design a program, we will play around on paper or on a whiteboard and come up with something like this as our first step. That should give a blueprint that you follow as you start to actually write code.

Refactoring our solution

Refactoring is taking an existing piece of code and reorganizing it so that the functionality is unchanged, but the design is simpler, or more flexible, or in some way better suited for where you want to go with it in the future.

We know we are always trying to avoid duplication of code. We know we are always trying to maximize separation of interface from implementation. Another maxim - and you may have heard this from a prof before - is this: if you have two tasks to perform, keep them separate, don't intermingle the two tasks in one piece of code. This a lower-tier maxim, because it's really more of a rule of thumb. Unlike the other two, it's OK to violate if the situation calls for it. But unless there's a darn good reason, you should follow it too.

So if you look at the actual code from the activity, you will notice that there are some methods in class SectionBank that are pretty similar:

  public void printAll() {
    for(Node p = first; p != null; p = p.next)
	System.out.println(p.data);
  }
  public void printMatchCourse(String course) {
    for(Node p = first; p != null; p = p.next)
      if (course.equals(p.data.getCourse()))
	System.out.println(p.data);
  }
  public Section find(String course, String number) {
    Node p = first;
    while (p != null
	   && !(course.equals(p.data.getCourse())
		&& number.equals(p.data.getNumber())))
      p = p.next;
    return p != null ? p.data : null;
  }
These make me uncomfortable on a number of levels:
  1. Duplication of code: printAll and printMatchCourse are clearly pretty similar. A bit less clearly, printMatchCourse and find are similar as well. Can we get rid of some of this duplication?
  2. Don't mix tasks: printMatchCourse and find both mix the tasks of iterating through the collection of sections and checking whether each sections "matches" some kind of criterion. Can we separate the code that determines whether we have a "match" from the code that iterates through the collection of sections?
  3. Separation of interface from implementation: This is a bit fuzzier, but somehow this idea of checking for matches is important, yet there is no "interface" for it ... it's all implementation. Can we offer an "interface" for match-checking?
If this was the end of the line for this code, I probably wouldn't worry about it, but if I intend to go on to do the Proj01 extra credit, I might want to refactor to get rid of these annoyances before going on. But how? The answer, is to make a class whose sole purpose is to provide methods for checking whether a section "matches" some criterion. There are, of course, several different "match" criteria, and we will use inheritance and polymorphism to provide them all.

With these three mini-classes in place, we can replace the three function printAll, printMatchCourse, and find with two methods ... and simpler methods at that!

  public void print(Matcher m) {
    for(Node p = first; p != null; p = p.next)
      if (m.match(p.data))
	System.out.println(p.data);
  }
  public Section find(Matcher m) {
    Node p = first;
    while (p != null && !m.match(p.data))
      p = p.next;
    return p != null ? p.data : null;
  }

Finally, we have to replace the calls in Proj01.main() of printAll, printMatchCourse and the old find method with our new methods:

                sched.printAll()  -becomes-  sched.print(new Matcher())
bank.printMatchCourse(sc.next())  -becomes-  bank.print(new MatchCourse(sc.next()))
  bank.find(sc.next(),sc.next())  -becomes-  bank.find(new MatchSection(sc.next(),sc.next()))
	

You can check out the full program

The Extra Credit

The premise behind the refactoring is that it puts us in a better position for whatever comes next. So does the refactoring we just did put us in a better position to do the extra credit?
An extra credit solution adds a command that lists sections that fit into the current schedule: Important: a section "fits" the current schedule if the course is not already in the schedule, and no period M-F,1-6, that covered or partially covered by the new section is covered or partially covered in the current schedule. I.e. if the "week grid" versions of the schedule and the section have an x in the same spot, they don't fit!
If you think about it, what we really need is to print out all the sections, just like always ... except that now the matching problem involves a more pieces. But after our refactoring, all this means is creating one new class, and it sits in its own file and we don't need to touch any of the existing work ... except for adding one little thing to Proj02.main():
      else if (comm.equals("fit"))
	bank.print(MatchFit.make(sc.next(),sched));
So let's see how this magical matcher works (and see how the class dependency diagram changes):

Notice how Proj01 has dependencies on Matcher and it subclasses, because Proj01 is where we choose which particular kind of Matcher we need, while SectionBank only depends on the base class, Matcher. That's important. From the perspective of the rest of the program, i.e. everything but Proj01, there are only Matchers out there, and which particular kind of Matcher is irrelevant. In fact, we may decide to collapse all the Matcher classes into one Node to clarify this: