Intermediate Computing

Homework #4 (a pseudo-programming activity)

Due : Friday, October 2nd at the start of class

Objective: To consider issues of good/bad design by "cleaning up" the world-of-zuul game

 


How is this activity different

What do I mean by "pseudo-programming"?

Well, in this assignment you will "program."  However, it is heavily guided programming.  That is, I am giving you some starting code and asking you to work through a set of VERY specific activities where you will have very little flexibility in the actual implementation.  In that regard it is code-writing, but it probably isn't true programming.

For this assignment you are encouraged, although not required, to work with a partner.  Before you begin, decide between yourselves who will be Partner A and who will be Partner B.  Please notice as you complete the assignment I will ask you to change roles several times.  Sometimes you will be the "coder" - the person in charge of the keyboard - and sometimes you will be the "driver" - the person in charge of telling the coder what to write and the person who records answers to questions.

While you will complete this assignment outside of my view, I ask that you follow this format very specifically.  When your job is to "code" you should only write the code that your partner tells you to write.  When the directions tell you to change roles you must change roles.  This "paired programming" style is a specific learning strategy that I think will help you learn the content of these materials.

To get started:

[Partner A should code while partner B should drive and record answers]

 

 

 

 

 

What happens if we want to make a "simple" change to our game?

[Partner B should code]

Now that you have looked at the code for the zuul game, you might wonder what the big deal is.  Why is this considered to be "bad" code?  We will start by having you make a couple of "simple" modifications to the game and looking at how difficult this process is when the code isn't well designed.

 

For example, suppose that we want to add a pool hall on the second floor above the campus pub.  In order to do this, we need to add in the ability to move up and down, and we need each room to know whether there are other rooms above it and/or below it.  By adding in the concept of an upExit and a downExit to each room in our game we can eventually tell the pub that it has an exit going up to the pool hall and tell the pool hall that it has an exit going down to the pub.

 

Once you do this, you need to have a way to set these values.  It would seem that the easiest way would be to update the setExits() method.

 

 

Ok, now that we have the functionality in place to allow for rooms to have exits up and down, let's add the pool hall  to our game.

[Switch partners.  Partner A should code]

 

OOPS!  The pub doesn't tell us that it has an up exit so that we have a way to get up to the pool hall.  How do we fix that?

 

DANG IT!  This didn't fix the problem.  How come?  Well, notice we just updated the printWelcome() method.  That method only runs at the start of the game.  What we REALLY wanted to change was the goRoom() method that gets called whenever you enter a new room (like the pub).

 

Ok, you probably figured this one wouldn't work since I told you to skip updating the direction list earlier in the goRoom() method.  You should observe that while the pub will TELL you it has an exit going up, it won't let you actually do this. 

 

 

 

Ok, that was harder than it should have been! - Removing Duplicate Code

[Partner B codes]

By now, some of you are doing some serious grumbling.  That should have been easy and it wasn't.  Why?

Hopefully, you recognize that this has been so hard because of code duplication. 

Code duplication is an indicator of bad design.  The Game class you have been working with contains a case of code duplication.  The problem with this is that any change to one version of the code must also be made to the other if we are to avoid inconsistency.  This increases the amount of work a maintenance programmer has to do, and it introduces the danger of bugs.  It happens very easily that a maintenance programmer finds one copy of the code and, having changed it, assumes that the job is done.  There is nothing indicating that a second copy of the code exists, and it might incorrectly remain unchanged.

To illustrate this point, notice that the printWelcome() and the goRoom() methods contained in the Game class both contain a block of code that :

As you observed, this is a problem when it comes time to modify the code.  You have to KNOW that both exist and you have to REMEMBER to update both of them when you make a simple change.

As we have discussed before, the easier thing to do is to remove duplicate code to a single, private, helper method. 

To do this, implement and use a separate printLocationInfo() method in your Game class and replace the duplicate code discussed above so that it refers to this helper method instead.

 

Reducing the coupling

[Partner A codes]

Another issue present in this code is the concept of coupling.  Coupling is the interdependency between two (or more) classes.  Coupling isn't bad (in fact, we want to design our code so that classes take advantage of each other) but we want to limit the degree of coupling.  While we can't avoid coupling, we can avoid high coupling.  This is when simple changes to one class require making significant changes to another class.  You witnessed first hand what it means to have high coupling when you added in the up/down exits in Part B -  what should have been a fairly simple change to the Room class turned out to require serious changes to the Game class as well (in fact, you made MORE changes to the Game class than you did to Room).

Why?  Because the Game class has LOTS of references to the instance variables stored inside the Room class. 

If you haven't noticed it yet, notice that the instance variables in the Game class are all public.  I have yelled about this several times this semester, but the reasons may not have been completely clear to you.  Here is a perfect example.  By having public instance variables in Room, Game has permission to access these variables all over the place.  While this might seem like a good thing, it often times makes it impossible to clean up the Room code without seriously breaking the Game code.

Let's push this issue a little more. 

The main problem with this "bad" code is that we have the Game class doing things that should be the responsibility of the Room class.  For example, in the goRoom() method, the Game class is digging around in the instance variables of the Room class to figure out what the value is for a particular exit so it knows what the nextRoom will be.  BUT WAIT, knowing and interpreting a Room's exits should be the job of the Room class.  We have our responsibilities in the wrong place.  Instead of having Game determine where to go next, we should just ask the Room class where to go next. 

All of this is a sign that it is time to refactor your code.  Refactoring is when you take code that works, but fix it so that it works better by having a better design. 

Let's reduce the coupling between Game and Room by moving some code around...

 

Even More reduction of coupling

[Partner B codes]

The last part helped us, but we still have a heavy link between Game and Room.  Notice that the printLocationInfo() method in the Game class is also peeking at all of the instance variables in instances of the Room class.  Why is the Game class determining which exits are valid for a given room.  Shouldn't that be the Room's job?

 

Let's reduce the coupling even more, by continuing to move some code around...

 

Skim down the code for the Game class.  If you have been following all of my directions, and have completed everything correctly, you should notice that Game no longer has ANY references to instance variables in the Room class.  Just to prove the point...

 

Why this change made our code better.

[Partner A codes]

So, you might think that this was mostly cosmetic, but notice that we have largely separated behavior from implementation now.  You have heard me talk about this, but what do I really mean???  Well, from now on, any changes to the way we store exits have been isolated within the Room class where they belong rather than out in the Game class (where they don't belong).

So let me make my point a bit clearer.

Suppose that we get tired of having six instance variables storing six exits - this is just a huge problem.  Ok, six might not be too bad, but maybe we decide to add four more exits on the diagonals (like NorthEast and SouthWest).

You have all taken a data structures course and you should be familiar with the concept of a HashMap (it goes by lots of other names too such as "dictionary" but the concepts are the same.  Recall that a HashMap is a non-ordered collection of items that stores things with a <Key,Value> pair.  That is, rather than saying, "let me see the 4th object in the collection"  you say, let me see the Value associated with the Key named someName.  For example, I might say, let me look up the phone number associated with the name "Schafer"

The Java API for the HashMap is located at http://java.sun.com/javase/6/docs/api/java/util/HashMap.html

However, the basic use is:

HashMap< String,String > phoneNumber;
...
phoneNumber = new HashMap< String,String >();
...

phoneNumber.put("Schafer","273-2187");
phoneNumber.put("OKane","273-7322");
...
number=phoneNumber.get("Schafer")

You must declare the data types of the keys and values in your HashMap at declaration and construction.  The keys must all have the same referential data type and the values must all have the same referential data type, but they need not be the same between the keys and values as mine are.

THUS, you could make a HashMap that takes a String such as "east" as the key and returns the actual Room to the east as a value.

Suppose you decided to replace these current six exit variables with a single HashMap.  To do this:

Notice that while this may require some serious changes to your Room class, it required changes ONLY to the Room class.

 

Reducing coupling between Game and CommandWords

[Partner B codes]

The previous activities showed us a way to reduce explicit coupling between Game and  Room.  It was called explicit coupling because we explicitly tied the two classes together by letting Game have access to Room's instance variables.  This project has even more examples of coupling in it.  However, this is an even worse form of coupling : Implicit Coupling.

Implicit coupling is a situation where one class depends on internal information of another, but this dependence is not immediately obvious. The tight coupling in the case of the public fields was not good, but at least it was obvious. If we change the public fields in one class, and forget about the other, the application will not compile any more and the compiler will point out the problem. In cases of implicit coupling, omitting a necessary change can go undetected.

We can see the problem arising if we try to add further command words to the game.

Suppose that we want to add the command look to the set of legal commands. The purpose of look is merely to print out the description of the room and the exits again (we ‘look around the room’) – this could be helpful if we have entered a sequence of commands in a room so that the description has scrolled out of view, and we cannot remember where the exits of the current room are.

We can introduce a new command word by simply adding it to the array of known words in the
validCommands array in the CommandWords class:
 

 

 

Ok, we have a couple of problems that need fixing...

We want this list to include an action when the player types in "look."  What seems like a good action?  Well, we recently wrote a method called printLocationInfo() that describes the location. That seems reasonable.

 

That half fixes our problem, but we still have a coupling problem we were likely not aware of.

 

 

While it would be easy to modify the printHelp() method in the Game class to now call this showAll() method.  Notice that the current design doesn't have Game talking to the CommandWords class (look at your UML diagram.  Notice that Game does not connect to CommandWords).  Instead, it talks to the Parser and the Parser talks to the CommandWords class.  For a variety of reasons, this is a pretty good design, although it makes your job a little more complicated


Submitting your assignment

Once you have everything completed and working it is time to prepare your assignment for submission.  Just to be safe, you may want to review my homework collection policies.

While you have modified existing code you still will add @author and @version tags for you and your partner if appropriate.  Please always list partner A first and partner B second regardless of who wrote code in that class.

If you worked with a partner you only need to upload/print this assignment once.  Upload the code using the account of partner A.

To upload your homework, log on to the homework submission system at:

and submit all six classes whether you modified them or not;

Print paper copies of these, staple them together in the order listed above, and bring this single, stapled, packet to class on the due date.

 


Thanks to David J. Barnes and Michael Kolling for the original inspiration behind this assignment.