CompSci 307
Fall 2021
Software Design and Implementation

Lab Exercise : Refactoring to use Design Patterns

This exercise is intended to help you to practice using Design Patterns to help organize and justify your design choices as well as direct your Refactoring.

Submission

At the end of lab, use Gitlab's Merge Request from your pair's forked repository's master branch back to the original organization repository's master branch to submit your group's refactored code. You only need to create one Merge Request, no matter how many commits you make, and you do not worry about potential conflicts since your request will not be approved (it is just an easy way for us to see what changes you made).

Make sure the Title of your Merge Request is of the form "lab_patterns - participating NETIDs".

Lab Workflow

In your pairs within project team, work together using Pair Programming, with separate cloned repositories or use IntelliJ's Code With Me feature, to practice removing different kinds of hardcoded assumptions from your code by turning them into values:

On Gitlab, one person should fork the original project lab_patterns into their own repository to allow pushing your group's changes:

  1. On the resulting project web page, go to Settings -> Members to add your partner so both people in the group can access the same repository
  2. Search for your partner's name and give them a Maintainer role in the project and choose Add to Project
  3. Then, on each person's computer, use git clone to make a local copy of the one forked repository

To ensure everyone does some coding, switch which person actually writes the code every 10-15 minutes (in industry this switch happens only 1-3 times per day). The person who is not actively coding can be advising, suggesting better names, looking up documentation, or searching the Internet for solutions to small problems you are likely to face, but not multi-tasking (i.e., doing their own work or socializing).

After you think each numbered task below is complete, make a GIT commit with an appropriate comment (i.e., there should be at least 7 commits in your project history). After each sub-project is complete, push your changes up to Gitlab (i.e., there should be at least 3 pushes done).

Understanding Patterns

Java came out around the same time as Design Patterns, so it was heavily influenced by them and, in fact, used many of their names directly for interfaces and classes (making it very confusing since patterns are intended to describe how multiple classes interact rather than one single class or abstraction).

Start by considering some real life examples from Java:

Look at the documentation or implementation of these classes, compare them to the standard description of the Design Pattern they implement. If there is something you like or dislike or something your learned about design during the discussion, make sure to back it up with a specific example.

Summarize the team's discussion for this part in the file DISCUSSION.md included within the lab repository.

Implementing Patterns

You already know how to implement Design Patterns since they use the basic Java skills and the SOLID design principles you have you have learned and practiced throughout the semester. In fact, you have likely already implemented at least one Design Pattern in your code without knowing there was a "name for it". Thus, Design Patterns are really just a good shorthand for referring to how a group of classes work together to achieve a design goal as well as describing the implementation's design trade-offs since just about any abstraction you create has a design goal.

Still, learning about Design Patterns can often open up your thinking about different ways to solve a design problem you are having — even though, as you can see from these examples, sometimes the final implementation can seem very simplistic:

Maze Template

Using the code in the project's maze package, complete the following refactoring to implement the Template Method pattern as the SearchAlgorithm superclass's step() method and reduce the code duplication in its subclasses.

  1. Carefully examine the code in the step() method of the four SearchAlgorithm subclasses in order to try to create a single common implementation in the SearchAlgorithm class.
    • The final superclass implementation will be composed of logic (conditionals and/or loops) along with calls to protected methods that you will create to capture the differences in how the solvers implement the algorithm's common actions.
    • You can see an example of this in the cleanUp() method called at the top of the step() method: some algorithms respond by doing nothing (RandomWalk and DFS) and some by marking the path followed to the goal (BFS and Greedy).
  2. Implement each newly created protected method in each subclass as needed (with its existing code from its version of the step() method)
  3. Replace the specific implementations in the SearchAlgorithm class with calls to the appropriate protected method to generalize its version of the step() method
  4. Remove the step() method completely from each subclass, so each has no public methods of its own (only constructors and implementations of the protected methods that differ from the superclass's default implementation if there is one)

The Template Method Pattern will likely be important in your game engine to help you create plugin points for your framework.

Simulation Iterator

Using the code in the project's matrix package, complete the following refactoring to implement the Iterator pattern in a simplified version of part of the Simulation project.

  1. Complete the inner class MatrixIterator by implementing Java's expected Iterator methods correctly (you may introduce any instance variables you want to keep track of the current element to return)
    • Implement the hasNext() method to return true only if there is at least one more element available
    • Implement the next() method to return the next sequential element in the matrix (i.e., going to the next row when necessary)
    • Run the tests in the main() method of the Main class to verify your implementation basically works

The Iterator Pattern can be used beyond just basic data structures, to access steps in an algorithm or levels in a game — even over repeated calls rather than directly in a traditional loop.