CompSci 308
Spring 2024
Advanced Software Design and Implementation

Lab Coding Exercise : Refactoring to use Design Patterns

In this lab, you will use Design Patterns to update three old projects (combined into the lab_patterns repository) to help organize and justify your design choices as well as direct your Refactoring.

In your current project team, complete the following steps:

  1. Practice Recovering from Mistakes in GIT (separate page)
  2. Understanding Patterns
  3. Setup Pair Programming Environment
  4. Refactoring to Patterns
  5. Submit Your Work

Please complete each step before moving onto the next.

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).

Each person should examine two of the following Design Patterns in the Java code by identifying the methods that represent the Design Pattern and compare them to its standard description. Describe the pattern with the entire group and show how it is used in the Java classes.

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

As a team, discuss the following questions:

Before Moving On: Add your names and summarize the group's discussion to the DISCUSSION.md file, then add, commit the file, and push to your lab_patterns repository.

Refactor to Patterns

Separate into pairs to complete these exercises, creating a separate commit for each numbered step, as well as switching who is working directly on the keyboard as your pair completes it.

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

Submitting your Work

At the end of lab, use Gitlab's Merge Request from your forked repository's main branch back to the original organization repository's main 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".