--- title: "Jam 06 - Exercise 4" tags: - 2 ๐Ÿ“ in writing - 3 ๐Ÿงช in testing - 4 ๐Ÿฅณ done - classes - uml - exceptions - enums --- <!-- markdownlint-disable line-length single-h1 no-inline-html --> <!-- markdownlint-configure-file { "ul-indent": { "indent": 4 }, "link-fragments": {"ignore_case": true} } --> {%hackmd dJZ5TulxSDKme-3fSY4Lbw %} # Exercise 4 - Completing the Cash Register System ## Overview - Exercise 4 Now that we have our `Money` enum and `Transaction` class with state management, it's time to implement the central component of our point-of-sale system: the `Register` class. This exercise will bring together all the components we've built so far, managing the cash drawer, processing transactions, and handling payments. You'll learn how to implement complex class relationships and manage collections of objects effectively. :::info ๐Ÿ”‘ **Key Concepts** - Factory Method Pattern for controlled object creation - Managing a cash drawer with Maps - Processing transactions and calculating change - Implementing utility classes - Integrating multiple components into a cohesive system ::: ## The Problem - Exercise 4 **Problem**: Our retail store needs a cash register that can: 1. Maintain a cash drawer with different denominations 2. Process customer transactions 3. Calculate change accurately 4. Validate operations based on current state 5. Provide a clean interface for cashiers This is a perfect opportunity to apply what you've learned about class relationships, collections, and state management. The `Register` class will serve as the central coordinator for our point-of-sale system, bringing together products, transactions, and money handling. ## Advanced Class Relationships Enter Advanced Class Relationships! In a retail environment, understanding how classes relate to each other is crucial. Let's explore these relationships through real-world examples: ### **Association** Think about a transaction at a store. Each transaction contains multiple products, just like in real life. This relationship is modeled as an association in UML: Example: Register "uses" Transaction ```plantuml @startuml class Register { -currentTransaction: Transaction } class Transaction { -items: List<Product> -total: double } Register "1" --> "0..*" Transaction : uses > @enduml ``` This diagram illustrates a classic association relationship where a single Register (shown by "1") can contain many Transactions (shown by ""0..*"). This relationship mirrors real-world operations: when you check out at a store, your transaction includes multiple products. You've already worked with a similar relationship in Jam05! Remember how your ProductCatalog maintained a collection of Products? That was also an association relationship, where one ProductCatalog could contain many Products. ### **Aggregation** Aggregation represents a "has-a" relationship where one class contains references to other classes, but those contained classes can exist independently. Think of a university course that has enrolled students: Example: Course "has" Students, but Students exist independently of any specific course ```plantuml @startuml class Course { -enrolledStudents: List<Student> -courseCode: String -instructor: String } class Student { -id: String -name: String -major: String } Course "1" o-- "*" Student : enrolls > @enduml ``` This diagram shows an aggregation relationship (note the hollow diamond) where a Course contains many Students. The hollow diamond indicates that Students can exist independently of any specific Course. In the real world, this is like a university registration system. If a course is canceled, the students still exist - they can enroll in other courses or continue their education. The students have an identity and purpose that exists independently of any particular course they take. ### **Composition** Composition is a stronger form of aggregation where the contained objects cannot exist without their container. If the container is destroyed, all contained objects are also destroyed: Example: A House "contains" Rooms, and Rooms cannot exist without a House ```plantuml @startuml class House { -rooms: List<Room> } class Room { -area: double -name: String } House "1" *--> "*" Room : contains > @enduml ``` This diagram shows a composition relationship (note the filled diamond) where a House contains many Rooms. The filled diamond indicates that Rooms cannot exist independently of the House. In the retail world, a Transaction might have LineItems that only exist as part of that Transaction. If the Transaction is deleted, all its LineItems are also deleted because they have no meaning outside that specific Transaction. ### **Dependency** Dependency is a weaker relationship where one class temporarily uses another class, typically as a method parameter or return type: Example: Register "depends on" PaymentUtility for calculations ```plantuml @startuml class Register { +calculateChange(amount: double): Map<Money, Integer> } class PaymentUtility { +{static} calculateOptimalChange(double, Map<Money, Integer>): Map<Money, Integer> } Register ..> PaymentUtility : uses > @enduml ``` This diagram shows a dependency relationship (note the dashed arrow) where Register depends on PaymentUtility. The dashed line indicates a temporary usage rather than a permanent relationship. In a retail context, this is like a cashier using a calculator to compute change. The cashier doesn't own the calculator or keep it permanently - they just use it when needed for a specific operation. ### **Inheritance/Generalization** Inheritance represents an "is-a" relationship where one class is a specialized version of another class, inheriting its attributes and behaviors: Example: Manager "is a" Employee ```plantuml @startuml class Employee { -name: String -id: int +work(): void } class Manager extends Employee { -team: List<Employee> +manageTeam(): void } @enduml ``` This diagram shows an inheritance relationship where Manager extends Employee. The arrow with the hollow triangle head points to the parent class. In retail systems, you might have different types of transactions that inherit from a base Transaction class. For example, a ReturnTransaction "is a" Transaction with additional behaviors for handling returned items. :::info ๐Ÿ’ก **Real-World Mapping** When designing retail systems, map your classes to physical objects: - Transaction โ†’ Paper receipt - Product โ†’ Item on the shelf - TransactionState โ†’ Status of the purchase process - Register โ†’ Physical cash register - Money โ†’ Physical currency This helps ensure your design matches real-world operations! ::: ## Required Steps - Completing the Cash Register System ### Task 1: Complete the UML Class Diagram Before we implement our final classes, let's update our UML diagram to include the Register and PaymentUtility classes. This will help us visualize the complete system and understand how all components interact. #### Adding Register to the UML Diagram The Register class is central to our system, managing transactions and the cash drawer. Here's what you need to add to your UML diagram: 1. Open your UML diagram in draw.io 2. Following the steps from Jam 5, import this text to create the new classes: ```text Register -cashDrawer: Map<Money, Integer> -currentTransaction: Transaction -completedTransactions: List<Transaction> -name: String -catalog: ProductCatalog -EXISTING_REGISTER_NAMES: Set<String> -- -Register(name: String, catalog: ProductCatalog) +createRegister(name: String, catalog: ProductCatalog): Register +startTransaction(): boolean -endTransaction(): boolean +voidTransaction(): boolean +addProductToTransaction(sku: String): boolean +removeProductFromTransaction(sku: String): boolean +getCurrentTransaction(): Transaction +processPayment(payment: Map<Money, Integer>): Map<Money, Integer> +getDrawerBalance(): int +addMoneyToDrawer(denomination: Money, count: int): boolean +removeMoneyFromDrawer(denomination: Money, count: int): boolean +getTransactionCount(): int +getTotalSales(): int +toString(): String PaymentUtility -- -PaymentUtility() +calculateTotal(Map<Money, Integer>): int +isPaymentSufficient(Map<Money, Integer>, int): boolean +calculateOptimalChange(int, int, Map<Money, Integer>): Map<Money, Integer> +formatAsDollars(int): String +combineMaps(Map<Money, Integer>...): Map<Money, Integer> ``` 3. Mark the static members. Static members are indicated by underlining the text: - Register: - EXISTING_REGISTER_NAMES - createRegister - PaymentUtility: Every method is static 4. Add relationships using the appropriate relationship type. #### Finalizing the UML Diagram 1. Review all relationships to ensure they accurately represent how the classes interact 2. Make sure all classes have proper visibility modifiers (+ for public, - for private) 3. Verify that method signatures include parameter types and return types 4. Export your updated diagram as `Jam06-uml.drawio.png` in your `jam06` folder > ๐Ÿ” **UML Checkpoint** > > Before proceeding, verify: > > - Both Register and PaymentUtility classes are added to your UML diagram > - All relationships are represented with the correct type of arrow > - Static members are properly underlined > - Class visibility modifiers are correct (+ for public, - for private) > - Your diagram is exported as `Jam06-uml.drawio.png` ### Understanding the Cash Drawer The cash drawer is a critical component of any register system. It needs to: 1. Track quantities of each denomination (`Money` enum) 2. Allow adding and removing money 3. Calculate the total value of all cash 4. Provide change in specific denominations A `Map<Money, Integer>` is the perfect data structure for this, as it allows us to: - Use `Money` enum constants as keys - Store quantities of each denomination as values - Easily update quantities when cash is added or removed - Iterate through all denominations when calculating totals or making change Here's a simplified example of how a cash drawer might be represented: ```java // Example cash drawer representation Map<Money, Integer> cashDrawer = new HashMap<>(); cashDrawer.put(Money.PENNY, 100); // 100 pennies cashDrawer.put(Money.NICKEL, 40); // 40 nickels cashDrawer.put(Money.DIME, 50); // 50 dimes cashDrawer.put(Money.QUARTER, 40); // 40 quarters cashDrawer.put(Money.ONE, 25); // 25 one-dollar bills cashDrawer.put(Money.FIVE, 15); // 15 five-dollar bills cashDrawer.put(Money.TEN, 10); // 10 ten-dollar bills cashDrawer.put(Money.TWENTY, 5); // 5 twenty-dollar bills ``` ### Understanding Utility Classes in Java Utility classes are a powerful design pattern in Java that provide a collection of related static methods. While not explicitly mentioned in our original requirements, they offer an elegant solution for organizing functionality that doesn't naturally belong to any specific object. :::success ๐Ÿ”‘ **What Makes a Good Utility Class** 1. **Contains only static methods** - No need to create an instance 2. **Cannot be instantiated** - Has a private constructor 3. **Has no state** - No instance variables 4. **Has a clear, focused purpose** - Methods relate to a single responsibility 5. **Is well-documented** - Clear explanations of what each method does ::: Think of utility classes as toolboxes filled with useful tools (methods) that anyone can borrow without taking the entire toolbox. #### Why Use Utility Classes? Utility classes solve several common design problems: 1. **Avoiding Code Duplication**: Instead of copying the same calculation logic in multiple places, centralize it in one location. 2. **Improving Readability**: Methods like `PaymentUtility.calculateTotal()` clearly communicate their purpose and location. 3. **Simplifying Testing**: Static methods with no dependencies are easy to test in isolation. 4. **Enhancing Maintainability**: When you need to fix a bug in a calculation, you only need to update it in one place. 5. **Separating Concerns**: Keep your business objects focused on their primary responsibilities while moving helper functionality to utility classes. #### Examples of Utility Classes in Java Java's standard library contains many utility classes you've likely used: - `Math` - mathematical operations - `Collections` - collection manipulation methods - `Arrays` - array manipulation methods - `StringUtils` - string operations (in Apache Commons) #### Implementing a Utility Class To create a proper utility class: 1. **Make the class final** - prevents inheritance (optional but recommended) 2. **Create a private constructor** - prevents instantiation 3. **Add documentation** - explain the purpose of the class 4. **Group related methods** - keep the class focused on a single responsibility 5. **Make all methods static** - allows calling without instantiation ### When NOT to Use Utility Classes Utility classes aren't appropriate when: - Methods need to maintain state between calls - Functionality naturally belongs to a specific object - You need polymorphic behavior (different implementations of the same method) - The operations would benefit from object-oriented principles like inheritance ### Task 2: Implement the PaymentUtility Class For our cash register system, we'll create a `PaymentUtility` class to handle common payment calculations that don't naturally belong to either the `Register` or `Transaction` classes. **Look at this example**: ```java /** * Utility class for payment-related operations. * This class provides static methods for common payment calculations * and validations used throughout the cash register system. */ public final class PaymentUtility { /** * Private constructor to prevent instantiation. * This is a key characteristic of utility classes. * * @throws UnsupportedOperationException if someone tries to create an instance */ private PaymentUtility() { throw new UnsupportedOperationException("Utility class cannot be instantiated"); } // Static methods will be implemented here... } ``` Notice how we've made the class `final` and added a private constructor that throws an exception if someone tries to create an instance. This follows best practices for utility class design. #### Creating Your Payment Utility For our cash register system, we'll create a `PaymentUtility` class to handle common payment calculations that don't naturally belong to either the `Register` or `Transaction` classes. Create a utility class called `PaymentUtility.java` with static methods for common payment operations. Here's a starting template with some methods implemented for you: ```java import java.util.Map; /** * Utility class for payment-related operations. * This class cannot be instantiated. */ public class PaymentUtility { /** * Private constructor to prevent instantiation. * This is a key characteristic of utility classes. * * @throws UnsupportedOperationException if someone tries to create an instance */ private PaymentUtility() { throw new UnsupportedOperationException("Utility class cannot be instantiated"); } /** * Calculates the total value of a collection of denominations. * * @param denominations Map of denominations and quantities * @return Total value in cents, or 0 if the map is null or empty */ public static int calculateTotal(Map<Money, Integer> denominations) { // TODO Implementation details... } /** * Determines if a payment is sufficient for a transaction. * * @param payment Map of payment denominations and quantities * @param transactionTotal Transaction total in cents * @return true if payment is sufficient, false otherwise */ public static boolean isPaymentSufficient(Map<Money, Integer> payment, int transactionTotal) { int paymentTotal = calculateTotal(payment); return paymentTotal >= transactionTotal; } /** * Calculates optimal change for a transaction using a greedy algorithm. * This method attempts to use the largest denominations first. * * @param amountTendered Total amount tendered in cents * @param transactionTotal Transaction total in cents * @param availableCash Map of available cash denominations and quantities * @return Map of change denominations and quantities, or null if change cannot be made */ public static Map<Money, Integer> calculateOptimalChange(int amountTendered, int transactionTotal, Map<Money, Integer> availableCash) { // TODO Review this implmentation - You can use it as is or modify it if you are feeling adventurous. // No guarantees it is perfect but it's fine to use as is. // Calculate change amount needed int changeAmount = amountTendered - transactionTotal; // If no change needed or invalid inputs if (changeAmount <= 0 || availableCash == null || availableCash.isEmpty()) { return Map.of(); // Empty map for no change } // Create a map to store the change Map<Money, Integer> change = new java.util.HashMap<>(); // Sort denominations in descending order (largest first) Money[] sortedDenominations = availableCash.keySet().toArray(new Money[0]); java.util.Arrays.sort(sortedDenominations, (a, b) -> b.getValue() - a.getValue()); // Greedy algorithm to make change for (Money denomination : sortedDenominations) { int denominationValue = denomination.getValue(); int availableCount = availableCash.get(denomination); if (availableCount <= 0 || denominationValue > changeAmount) { continue; } // Calculate how many of this denomination we need int neededCount = Math.min(changeAmount / denominationValue, availableCount); if (neededCount > 0) { change.put(denomination, neededCount); changeAmount -= neededCount * denominationValue; } // If we've made exact change, we're done if (changeAmount == 0) { break; } } // If we couldn't make exact change if (changeAmount > 0) { return null; } return change; } /** * Formats a cent amount as a dollar string. * * @param cents Amount in cents * @return Formatted string in dollars (e.g., "$10.25") */ public static String formatAsDollars(int cents) { // TODO Implementation details... } /** * Combines multiple payment maps into a single map. * This is useful for aggregating payments from different sources. * * @param payments Variable number of payment maps to combine * @return A new map containing the combined quantities of all denominations */ @SafeVarargs public static Map<Money, Integer> combineMaps(Map<Money, Integer>... payments) { // TODO Review this implmentation - You can use it as is or modify it if you are feeling adventurous. // No guarantees it is perfect but it's fine to use as is. // Create a new EnumMap to store the combined result Map<Money, Integer> result = new java.util.EnumMap<>(Money.class); // Process each payment map for (Map<Money, Integer> payment : payments) { if (payment == null || payment.isEmpty()) { continue; // Skip null or empty maps } // Add each denomination from the current payment to the result for (Map.Entry<Money, Integer> entry : payment.entrySet()) { Money denomination = entry.getKey(); Integer quantity = entry.getValue(); if (quantity <= 0) { continue; // Skip non-positive quantities } // Add to existing quantity or set new quantity result.merge(denomination, quantity, Integer::sum); } } return result; } } ``` Implement the remaining methods in the utility class. When implementing, think about edge cases: - What if the denominations map is null or empty in `calculateTotal`? - How should you format negative values in `formatAsDollars`? The `calculateOptimalChange` method demonstrates how to handle edge cases like: - Checking for null or empty inputs - Handling situations where exact change can't be made - Using a greedy algorithm to optimize denomination selection :::info ๐Ÿ”ง **IntelliJ TODO View** IntelliJ provides a powerful `TODO` view that helps you track and manage `TODO`s in your code. To use it: 1. Look for the "TODO" tool window button on the bottom of your IDE 2. If not visible, go to View โ†’ Tool Windows โ†’ TODO 3. The TODO view will show all TODO comments in your project 4. Double-click any TODO to jump to that location in the code This is especially helpful when implementing a new class, as you can add TODOs for methods you still need to implement. ::: ### Task 3: Implement the Register Class Now that you've designed the Register class in your UML diagram, it's time to implement it. Remember that your implementation should follow the UML design you created earlier. But first, did you notice that the constructor is private? This is a hint that we are going to use the Factory Method Pattern to create instances of the Register class. New Design Pattern Alert! ### Understanding the Factory Method Pattern The Factory Method Pattern is a powerful design pattern that belongs to the "creational" family of patterns. But what exactly is it, and why should we use it in our Register class? **What is the Factory Method Pattern?** The Factory Method Pattern is a design pattern that provides an interface for creating objects without specifying their concrete classes. It's like having a specialized "object creation" service that handles all the complexity of creating new instances. Think of it like a real-world factory: you don't need to know how to build a car from scratch - you just tell the factory what kind of car you want, and they handle all the details of creating it for you. **Why & When to Use the Factory Method Pattern?** There are several compelling reasons to use this pattern in our Register class: 1. **Controlled Object Creation**: By centralizing object creation in a factory method, we can enforce validation rules, ensure proper initialization, and maintain system-wide constraints. 2. **Encapsulation of Creation Logic**: The pattern hides the complex logic of creating objects, making the client code simpler and more focused on using the objects rather than creating them. 3. **Name Uniqueness**: For our Register class, we need to ensure each register has a unique name. The factory method gives us a central place to enforce this constraint. 4. **Future Flexibility**: If we later need to create different types of registers (e.g., ExpressRegister, SelfCheckoutRegister), the factory method can decide which type to create based on parameters or context. 5. **Testability**: Factory methods make it easier to mock objects during testing, as you can override the factory method in test subclasses. **Why Use Static Methods for Factories?** You might wonder why we're using a static method for our factory. Here's why: 1. **Global Access**: Static methods can be called without an instance, making the factory accessible from anywhere in the application. 2. **No Dependency on Factory Instance**: Clients don't need to create or maintain a factory instance - they can simply call the static method directly. 3. **Singleton-like Behavior**: The static collection of register names acts like a singleton, maintaining application-wide state about existing registers. 4. **Clear Intent**: A static factory method clearly communicates its purpose - it's a dedicated method for creating instances of a class. ### Task 4: Implement the Register Class Now, let's implement the Register class according to our UML design. This class serves as the central coordinator for our point-of-sale system. 1. Start by creating a new file `Register.java` with the instance variables based on your UML design. 2. Then, implement the private constructor and static factory method: ```java /** * Private constructor for a Register with the specified name. * Only accessible through the factory method. * * @param name the name of this register * @param catalog the product catalog to use for product lookups */ private Register(String name, ProductCatalog catalog) { this.name = name; this.cashDrawer = new HashMap<>(); this.currentTransaction = null; this.catalog = catalog; this.completedTransactions = new ArrayList<>(); } /** * Factory method to create a new Register with the specified name. * This method validates the input, ensures name uniqueness, and controls the creation process. * * @param name the name for the new register * @param catalog the product catalog to use for product lookups * @return a new Register instance, or null if the name is invalid or already exists */ public static Register createRegister(String name, ProductCatalog catalog) { // TODO: Review and understand this implementation. You can use it as is or modify it if you are feeling adventurous. // Validate the name parameter if (name == null || name.trim().isEmpty()) { return null; } // Check if the name is already in use if (EXISTING_REGISTER_NAMES.contains(name)) { return null; } // Add the name to our set of existing names EXISTING_REGISTER_NAMES.add(name); return new Register(name, catalog); } ``` Understand what this code is doing. If you aren't sure, ask an AI! Do you see how you might have been able to modify your Product class to autogenerate sequential SKUs? This is a good example of how you can use the Factory Method Pattern to control the creation of objects. 3. Next, implement the transaction management methods (some hints are provided for you but your actual implementation is up to you. Some hints are overkill depending on your Transaction class implementation): ```java /** * Starts a new transaction. * * @return true if a new transaction was successfully started, false otherwise */ public boolean startTransaction() { // TODO: Implement this method // Check if there's already an active transaction // Create a new transaction if there isn't one // Return true if successful, false otherwise } /** * Ends the current transaction, marking it as completed. * * @return true if the transaction was successfully ended, false otherwise */ private boolean endTransaction() { // TODO: Implement this method // Check if there's an active transaction // Check if the transaction can be completed // Mark the transaction as completed // Add it to completedTransactions // Set currentTransaction to null // Return true if successful, false otherwise } /** * Voids the current transaction. * * @return true if the transaction was successfully voided, false otherwise */ public boolean voidTransaction() { // TODO: Implement this method // Check if there's an active transaction // Check if the transaction can be voided // Mark the transaction as voided // Set currentTransaction to null // Return true if successful, false otherwise } ``` 4. Implement the product management methods: ```java /** * Adds a product to the current transaction by SKU. * * @param sku the SKU of the product to add * @return true if the product was successfully added, false otherwise */ public boolean addProductToTransaction(String sku) { // TODO: Implement this method // Check if there's an active transaction // Look up the product in the catalog // Add the product to the transaction // Should handle well (e.g., work as if it's unsuccessful) if it's in the wrong state (although this is the job of Transaction if Transaction is implemented correctly) // Return true if successful, false otherwise } /** * Removes a product from the current transaction by SKU. * * @param sku the SKU of the product to remove * @return true if the product was successfully removed, false otherwise */ public boolean removeProductFromTransaction(String sku) { // TODO: Implement this method // Check if there's an active transaction // Check if the transaction is in the correct state // Look up the product in the catalog // Remove the product from the transaction // Should handle well (e.g., work as if it's unsuccessful) if it's in the wrong state (although this is the job of Transaction if Transaction is implemented correctly) // Return true if successful, false otherwise } /** * Gets the current transaction. * * @return the current transaction, or null if no transaction is in progress */ public Transaction getCurrentTransaction() { return currentTransaction; } ``` 5. Implement the payment processing method (this is the most complex method to implement. Remember that you can use the PaymentUtility class to help you!!! Check those methods you have available to you!): ```java /** * Processes a payment for the current transaction. * * @param payment a map of money denominations and their counts * @return a map representing the change given, or null if payment failed */ public Map<Money, Integer> processPayment(Map<Money, Integer> payment) { // TODO: Implement this method // Check if there's an active transaction // Check if the transaction is in the correct state // Set the Transaction to be ready to process payment // Determine the transaction total // Determine the payment total from the customer // Check if the payment is sufficient // Calculate change // Update the cash drawer (money going in and going out) // Complete the transaction // Return the change (return null if failure at any point) } ``` 6. Implement the cash drawer management methods: ```java /** * Gets the current balance of the cash drawer in cents. * * @return the total value of all money in the drawer in cents */ public int getDrawerBalance() { return PaymentUtility.calculateTotal(cashDrawer); } /** * Adds money to the cash drawer. * * @param denomination the denomination to add * @param count the quantity to add * @return true if the money was successfully added, false otherwise */ public boolean addMoneyToDrawer(Money denomination, int count) { // TODO: Implement this method // Validate the inputs // Update the cash drawer // Return true if successful, false otherwise } /** * Removes money from the cash drawer. * * @param denomination the denomination to remove * @param count the quantity to remove * @return true if the money was successfully removed, false otherwise */ public boolean removeMoneyFromDrawer(Money denomination, int count) { // TODO: Implement this method // Validate the inputs // Check if there's enough of the denomination in the drawer // Update the cash drawer // Return true if successful, false otherwise } ``` 7. Implement the reporting methods: ```java /** * Gets the total number of completed transactions. * * @return the number of completed transactions */ public int getTransactionCount() { return completedTransactions.size(); } /** * Gets the total sales amount in cents. * * @return the total sales amount in cents */ public int getTotalSales() { // TODO: Implement this method // Sum the total of all completed transactions // Return the total in cents } @Override public String toString() { // TODO: Implement this method (you can start with IntelliJ's generator! You can modify it if you want to, maybe utlize some of the methods you implemented earlier (here and in PaymentUtility)) } // You may need to add a closing brace here if you copied these all at one. If you did them step by step you probably already added a closing brace here. ``` ### Task 5: Test with RegisterDriver > โš ๏ธ **Important Note About Method Names** > > The driver program uses specific method names that may not match your implementation. For example, if your Register class uses `addItem()` but the driver uses `addProductToTransaction()`, you'll need to modify the driver to match your method names. This is expected and normal - just make sure to update all instances of the method name in the driver to match your implementation. To verify that your implementation works correctly, we'll use a driver program that tests various scenarios. 1. Copy the RegisterDriver.java file from the course directory: ```bash # If working on your laptop (replace userid with your Bucknell username): scp "userid@linuxremote.bucknell.edu:/home/csci205/2025-spring/student/jam06/RegisterDriver.java" src/main/java/jam06/ # If working on linuxremote: cp "/home/csci205/2025-spring/student/jam06/RegisterDriver.java" src/main/java/jam06/ ``` 2. Review the driver code to understand the test scenarios: - Normal transactions with exact payment - Transactions requiring change - Voided transactions - Error conditions (insufficient funds, invalid products) - Cash drawer operations 3. Run the driver program and verify that all scenarios work correctly 4. If you encounter errors: - Read the error message carefully - Look for the line number where the error occurred - Check your implementation against the requirements - Fix the issue and rerun the driver 5. Feel free to add your own test cases to the driver to verify your implementation but you should at least be able to pass the existing test cases with modifying only things like method names. If you have to modify more than that, you may have issues with your implementation. > ๐Ÿ” **Implementation Checkpoint** > > Before finalizing your implementation, verify: > > - PaymentUtility works correctly for all test cases > - Register correctly manages transactions and the cash drawer > - All methods validate input and handle edge cases > - The driver program runs without errors > - Your implementation follows your UML design > - All classes have proper JavaDoc documentation ### Task 6: Final Review and Refinement Before submitting your work, take some time to review and refine your implementation: 1. **Code Review** - Check that all classes follow the UML design - Verify proper encapsulation (private fields, public methods) - Ensure all methods have appropriate JavaDoc comments - Confirm that error handling is robust 2. **UML Diagram Review** - Verify that your UML diagram accurately reflects your implementation - Check that all relationships are correctly represented - Make sure all methods and fields are included 3. **Testing** - Run the RegisterDriver to verify all functionality - Fix any issues that arise during testing - Ensure all test scenarios pass 4. **Documentation** - Review your JavaDoc comments for completeness - Make sure your UML diagram is clear and well-organized - Verify that your code follows the course style guidelines ## Save Your Work - Exercise 4 Once your implementation is complete and all tests pass, it's time to save your work: **Export your final UML diagram**: 1. Update your UML diagram if you made any changes during implementation 2. Export the final version as `Jam06-uml-FINAL.drawio.png` in your `jam06` folder (using the steps from the last jam) **Stage your changes**: ```bash git add src/main/java/jam06/Jam06-uml-FINAL.drawio.png src/main/java/jam06/Register.java src/main/java/jam06/PaymentUtility.java src/main/java/jam06/RegisterDriver.java ``` **Verify what files are still uncommitted (no red files/folders)**: ```bash git status # Resolve any red files/folders ``` **Commit your work**: ```bash git commit -m "jam06: Implement Register and PaymentUtility classes" ``` Your working directory should now be clean. :::success ๐Ÿ”‘ **Key Takeaways from Exercise 4** - The Factory Method Pattern provides controlled object creation - Utility classes centralize related functionality for reuse - Maps are excellent for representing key-value relationships like a cash drawer - Complex systems can be broken down into collaborating components - Proper validation ensures system integrity - Static methods and fields can maintain application-wide state - HashMap and EnumMap provide efficient map implementations :::