CompSci 308
Spring 2023
Advanced 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.

Submitting your Work

At the end of lab, use Gitlab's Merge Request from your forked repository's master branch back to the original organization repository's master branch to submit your group's discussion summary and refactored code.

Make sure the Title of your Merge Request is of the form "lab_patterns - everyone's 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 complete the following activities today:

  1. On Gitlab, one person should fork the original project lab_patterns into their own repository to allow pushing your group's changes
    • On the resulting project web page, go to Project Information -> Members (in the upper left corner) to add your partner so both people in the group can access the same repository
    • Search for your partner's name and give them a Maintainer role in the project and choose Add to Project
  2. In Terminal, both students should use git clone to copy the one forked repository to their individual machines so they can work on their own separate copies
  3. In IntelliJ, both students should Open the cloned folder to create a new project on their personal machine
  4. Together, discuss testing strategies and think of as many tests as you can
  5. As a group, work using Pair Programming to implement the changes described below
    • Create a separate commit for each numbered step, as well as switching who is working directly on the keyboard as your pair completes it, to verify you are following the spirit of the exercise

Understanding Patterns

Technically, you already know how to implement Design Patterns since they use the basic Java skills and the SOLID design principles 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 explicitly 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 the examples below, sometimes the final implementation can seem very simplistic.

Also note that Java was created around the same time, 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).

Identify the methods of these Java classes that represent their respective Pattern and compare them to its standard description:

You can also look at their implementation (such as this Template Method or this Builder class).

In your discussion summary, 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.

Implementing Patterns

Bins: Strategy

Using the code in the project's bins package, complete the following refactoring to implement the Strategy pattern:

  1. Replace a conditional with polymorphism in the Bins class, using Lambdas to easily create the subclasses
    • Change the fitDisksAndPrint() method so that it takes a third parameter of type Consumer<List<Integer>> and replace the entire chain of conditionals with a single call to the parameter's accept() method, passing the correct list variable.
      Note, you may want to cut the conditional code since you will need it later for the body of the methods used in your Lambdas.
    • Within the main() method, change the calls to the fitDisksAndPrint() method to provide this third parameter using Lambdas rather than writing out the complete code to declare and instantiate class instances that implement Consumer<List<Integer>>. Two of the Lambdas will transform the list parameter in some way (e.g., sort, shuffle, or reverse) and one will be empty (i.e., do nothing).
      Note, you should be able to use the code between the braces of each if statement directly, one per call, using the Lambda's arrow, ->, syntax.
  2. Replace separate hardcoded method calls with a loop based on a collection of the actions
    • Replace the three constant strings that describe the WORST_FIT algorithms with a constant Map<String, Consumer<List<Integer>>>, i.e., the description and its associated action.
      Note, it can be immediately initialized using the Map.of() method with the three Lambdas you previously hardcoded in the main() method.
    • Within the main() method, replace the three calls to the fitDisksAndPrint() method with a loop using the constant Map to supply the parameter values for each call instead of the current constants and actions
Roulette: Factory

Using the code in the project's game package, complete the following refactoring to implement the Factory pattern

  1. Create a BetFactory class that manages possible Bet subclasses and returns the one chosen by the user so the Game no longer has to manage that responsibility
    • To start, simply move the list of hardcoded subclasses and the promptForBet() method from the Game class to the new BetFactory class
    • Add a public method named makeBet(), that take no parameters and return a Bet superclass, and will be called from the Game class (instead of promptForBet())
  2. Change the hardcoded collection of concrete Bet subclasses to use reflection (note, the available bets will still be hardcoded)
    • Change the List<Bet> to a List<String>, where the strings represent the complete name of each Bet subclass (i.e., the class's package as well as its name).
    • Create a new method makeBetFromString() that gets a Class object representing the Bet subclass named in the given string using the forName() method, then gets its Constructor object, and calls the newInstance() method (and catches its many exceptions) instead of using the instance directly in the previous list.
      Note, you can use either the given default constructor for each Bet subclass or create some logic to provide the description and odds for each subclass — whichever makes more sense for you to practice for your project.
  3. Remove the hardcoded list and replace it with a list of class names from a properties file instead
    • Create a properties file and, in the BetFactory constructor, read it using a ResourceBundle object to determine which subclasses are possible to create so that nothing is hardcoded
      Note, the reflection code itself should likely not need to change because you are using the properties file to populate the List instance variable(s).
      Note, the properties file should map either a general bet name to the complete Bet subclass name (for the default constructor) or the complete Bet subclass name to its odds and description, on one line separated by a comma for easy parsing by your program (for the two argument constructor) — whichever makes more sense for you to practice for your project.
Simulation: Iterator

Using the code in the project's matrix package, complete the following methods to implement the Iterator pattern

  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