1 Reminder: Setup
2 The Visitor Infrastructure
3 Adding Methods
4 Adding Characters
5 Moving Characters
6 Putting the Pieces Together
7 Moving Forward: Writing Extensible Software

Lab 4 Advanced: Revisiting Extensible Programming in Java

In Lab 2 advanced, we looked at extending the code for an adventure game with new characters and new operations. There, we tried using subclasses to solve the problem, and found that we could not easily add both characters and operations without editing existing code (the exercise is easy if you edit the existing classes). Now that we have Visitors, which are designed to add new functions to existing class hierarchies, let’s revisit the extensibility problem.

If you did not do lab 2 advanced, you may start with either these problems or those problems.

1 Reminder: Setup

You are (still) writing a small adventure game with princes and frogs. For each character, the game stores its position (a single integer). This game takes place in a magical world though: there are also displaced characters, that appear at a position other than where they are actually located. For displaced characters, the game maintains an offset to the real location and the character at the real location. The basic class definitions are in this file.

2 The Visitor Infrastructure

We want to be able to write various methods that process all of the characters in a game. To save us from having to add methods to every character for every new operation, we want to set up visitors on the characters.

Define an IProc interface with a process method for each character (i.e., processPrince, etc). Edit each character class to add a visit method that consumes an IProc and calls the appropriate method for the class from within the IProc.

From this point on do not edit existing classes. You may extend existing classes/interfaces with new ones, but you may not edit any existing class or interface.

3 Adding Methods

Use your Visitor infrastructure to implement an atPosition method on characters. This method takes a position (an integer) as an argument and returns true if the visited character is at that position. Displaced characters need to be checked at their offset from the given position.

In part, this question teaches you how to pass additional arguments (other than the character being visited) to functions passed as arguments. In this case, your visit method takes only a character, but the position to check at also needs to be an argument to atPosition. Such additional arguments need to be provided as arguments to the constructor for the atPosition visitor. If you have to change the argument, you must create a new instance of the visitor with the new argument value.

Write test cases to confirm that each character type responds properly to atPosition.

4 Adding Characters

As the magic continues, characters can be bound together in groups. Characters in a group each maintain their own position, but any game action that moves one character in a group would move them all. Here is a basic class for grouped characters:

  class Grouped extends Character {

    Character c1;

    Character c2;

  

    Grouped(Character c1, Character c2) {

      this.c1 = c1;

      this.c2 = c2;

    }

  }

Without editing your atPosition visitor class, extend your code to check for the position of grouped characters. A grouped character is at a given position if any of its members is at that position.

5 Moving Characters

Now, write an additional visitor on your collection of characters. This visitor implements a function move that consumes an integer and moves a character by that amount (assume a negative integer moves one direction and a positive integer moves in the other direction). The move function should return a new character at the new location (do not use assignment to edit the location within the object).

Test that you can move characters, including grouped characters that contain displaced characters.

6 Putting the Pieces Together

Now, write some tests of atPosition on characters that have been moved using move. Be sure to test various nestings of characters, including grouped characters that contain displaced characters and displaced grouped characters.

What went wrong and why?

If you did Lab 2, contrast the problem in each of these two labs: which kinds of extensions went smoothly, and which were problematic? Did you observe a pattern in the problematic cases?

7 Moving Forward: Writing Extensible Software

Thinking back to the extensibility problem you studied in Lab 2, we see a similar pattern here: hard-coding constructor names breaks extensibility. To get around this, you need to use either virtualized constructors or the Factory Pattern. If you have time, redo these problems using the Factory Pattern.