HW3: Violet's Vending Venture¶

Image source: vecteezy
Learning Objectives¶
- Implement a UML diagram for Classes.
- Implement method overloading in code.
- Write a program that uses ArrayList for data storage.
- Use enums to model fixed categories with associated values.
- Apply exception handling to enforce data validation and system rules.
Overview¶
A JMU alum, Violet is starting a vending machine around different locations on Campus. She wants the business to be profitable, thus to plan she needs your help writing a Java program that simulates a vending machine interaction. The goal of the program is to calculate the profit made by each individual machine, as well as the total profit from all machines combined.
This vending machine sells multiple types of products, each with its own cost and selling price. The program should handle the logic to calculate profit for each item sold, then total the profits for the whole machine.
The program should then display the individual machine profits and also the total business profit.
The Classes to be Written¶
The following UML diagram shows all of the classes and relationships between them. Note that dependencies (weak relationships) are omitted from the diagram.
classDiagram
direction TB
class ProductType {
<<enumeration>>
+WATER#40;0.25, 1.00#41;
+SODA#40;0.35, 1.50#41;
+CANDY#40;0.40, 1.25#41;
+CHIPS#40;0.70, 2.00#41;
+COOKIES#40;0.85, 2.25#41;
- cost : double
- price : double
-ProductType(cost: double, price: double)
+getCost() double
+getPrice() double
}
class Product {
+ ROUND_PRICE : int = 25$
- type : ProductType
- name : String
- cost : double
- price : double
+ Product(type: ProductType, name: String)
+ setCustomPrice(price: double)
+ getName() String
+ getType() ProductType
+ getPrice() double
+ getCost() double
+ toString() String
}
class Slot {
+ SLOT_SIZE: int = 10$
- type : ProductType
- products : ArrayList~Product~
+ Slot(type: ProductType)
+ load(product: Product, count: int) int
+ nextProduct() Product
+ buyOne() Product
+ toString() String
}
class VendingMachine {
- totalProfit : double$
- machineProfit: double
- slots: Slot[]
+ getTotalProfit() double$
+ resetTotalProfit()$
+ VendingMachine(types: ProductType[])
+ load(slotNum: int, count: int, product: Product)
+ nextProduct(slotNum: int) Product
+ buy(slotNum: int) boolean
+ buy(type: ProductType) boolean
+ getSlotCount() int
+ getMachineProfit() double
+ toString() String
}
class Simulator {
- vmList: ArrayList<VendingMachine>
+ Simulator(vmList: ArrayList<VendingMachine>)
+ addVM(vm: VendingMachine)
+ simulate(pCount: int) double
}
Simulator --> VendingMachine : simulates
VendingMachine *-- Slot : is made up of
Product --o Slot : contains
ProductType <-- Product : has a
Slot --> ProductType : has a
%% VendingMachine ..> Product : sells
%% VendingMachine ..> ProductType : uses
UPDATE 9/30: Small correction to getTotalProfit() - the return type should be a double, not an int.
Where to Start?
If you look at the UML diagram, you can see how certain classes depend on others. For example, Product depends on only ProductType, while Slot depends on both Product and ProductType.
Start by coding the class with the fewest dependencies – ProductType.
You will be writing three primary classes: Product, Slot and VendingMachine. The Simulator class will simulate user interaction with the vending machine, utilizing the functionality of all the core classes together. Finally, the ProductType enum will represent predefined categories of products, each associated with a default cost and price. It is used by the Product class to determine pricing and by other classes to enforce type consistency.
All the classes must be in the hws.hw3 package. The get methods for all the classes must return the appropriate instance variable values.
ProductType.java¶
The ProductType enumeration (more about enum📖) defines the fixed set of product categories available in the vending machine. Each product type is associated with two values:
cost : double– the amount it costs the vending machine company to stock the item.price : double– the amount the customer is charged for the item.
This enum is used throughout the system like to categorize products or apply default pricing.
UPDATED 10/3: Note that, although it is an enum, the ProductType enum must still have a constructor. The constructor takes in a cost and a price, and sets the corresponding instance attributes. This constructor should be private, or better, omit the visibility of your constructor like so:
public enum ProductType {
...
ProductType(double cost, double price) {
...
}
...
}
The UML Class Diagram¶
The following UML diagram and note provides an overview of the enum.
classDiagram
class ProductType {
<<enumeration>>
+WATER#40;0.25, 1.00#41;
+SODA#40;0.35, 1.50#41;
+CANDY#40;0.40, 1.25#41;
+CHIPS#40;0.70, 2.00#41;
+COOKIES#40;0.85, 2.25#41;
- cost : double
- price : double
-ProductType(cost: double, price: double)
+getCost() double
+getPrice() double
}
The Product Class¶
The Product class models an individual item type in a vending machine. Each product is associated with a ProductType and also has a name. The default cost and price values are based on the ProductType.
Some products may need a custom price, thus the setCustomPrice() method. Violet has said that she is unwilling to deal with anything but quarters, dollar bills, and debit cards so all prices are kept divisible by 25 cents. Costs to the vending machine company can be any penny value. All of the get methods perform the expected operation.
The UML Class Diagram¶
The following UML class diagram provides an overview of the attributes and methods in this class.
classDiagram
class Product {
+ ROUND_PRICE : int = 25$
- type : ProductType
- name : String
- cost : double
- price : double
+ Product(type: ProductType, name: String)
+ setCustomPrice(price: double)
+ getName() String
+ getType() ProductType
+ getPrice() double
+ getCost() double
+ toString() String
}
Detailed Design Specifications¶
-
Product(ProductType type, String name)This constructor takes the type and name as parameters and sets the instance variables appropriately. -
setCustomPrice()takes a custom price and sets it for the product instance. Price should be rounded to the nextROUND_PRICEcents above the amount given if the amount given is not already divisible byROUND_PRICE.
Note: ROUND_PRICE represents the value in cents to which all prices should be rounded. If the price given is not greater than the associate cost, the price should be the next ROUND_PRICE- divisible-value that is greater than the cost.
Example
Let’s say we’re setting a custom price of $2.35, but the system only allows prices in multiples of 25 cents (like $2.00, $2.25, $2.50, etc.). Since $2.35 is not a multiple of $0.25, the system needs to round it up to the next closest allowed price.
- $2.35 is between $2.25 and $2.50
- The next allowed price (going upward) is $2.50
So, when you enter $2.35, the setCustomPrice() should convert it to $2.50 to match the required rounding rule.
toString()method will return a String of the instance variables of the class exactly as shown below. Assuming an instance withtype"CANDY",name"M&Ms",costof$1.02, and apriceof$1.25,toString()would return:
Candy: M&Ms Cost: 1.02 Price: 1.25.
The Slot Class¶
The Slot class models a slot in the vending machine. Slots are loaded from the rear, and purchases are removed from the front. This ensures that the items that have been in the machine the longest are sold before newly added items.
-
SLOT_SIZE: int = 10- the maximum number of products that a slot in the vending machine can hold. -
products: ArrayList<Product>- models the actual products that are in the slot. Removing the one at the front models buying one of the products in the slot and all of the others are moved forward similar to an actual vending machine.
The UML Class Diagram¶
The following UML class diagram provides an overview of the attributes and methods in this class (which must be in the hws.hw3 package).
classDiagram
class Slot {
+ SLOT_SIZE: int = 10$
- type : ProductType
- products : ArrayList~Product~
+ Slot(type: ProductType)
+ load(product: Product, count: int) int
+ nextProduct() Product
+ buyOne() Product
+ toString() String
}
Detailed Design Specifications¶
In addition to the specifications contained in the UML class diagram, this class must conform to the following specifications.
-
Slot(ProductType type)The Slot() constructor creates an empty array list of products and sets the type. -
load(Product product, int count)checks if the given product’sProductTypematches the slot’sProductType. Loads the slot with up to count products, without exceeding capacity. Use SLOT_SIZE as the default value to fill the slot completely. Return the number of products added accordingly. This method should throw anIllegalArgumentExceptionwith the message"Product type does not match slot product type"if the type of the product you are trying to load does not match the slot's product type. -
nextProduct()returns a reference to the next product available for purchase, without actually removing it from the slot. If the slot is empty, this method will return null. -
buyOne()simulates the purchase of one item from the perspective of the slot. That means no money is dealt with here, rather the slot is checked to make sure there is product to buy and then one product is removed from the front of the ArrayList modeling the slot. If a product is successfully removed from the slot, it is returned, otherwise null is returned. -
toString()returns a String exactly like the one below for a slot with 6 M&M products. Items should start with the front slot and end with the rear slot.
SlotCount: 6 of
Candy: M&Ms Cost: 1.02 Price: 1.25.
Candy: M&Ms Cost: 1.02 Price: 1.25.
Candy: M&Ms Cost: 1.02 Price: 1.25.
Candy: M&Ms Cost: 1.02 Price: 1.25.
Candy: M&Ms Cost: 1.02 Price: 1.25.
Candy: M&Ms Cost: 1.02 Price: 1.25.
Hint
Don’t forget to make use of other toString() methods.
The VendingMachine Class¶
The VendingMachine class is a simple vending machine. Exact change is required so it is assumed if someone is buying something they have inserted the right amount of money or have used a debit card. The get methods return the appropriate instance variable values.
-
totalProfit: double– this models the total profit for all of theVendingMachinestogether. It is the sum of thepriceof everyproductbought from all of the machines minus the sum of thecostof all the products ever put in all of the machines. -
machineProfit: double– this models the long term profit for this particular machine. It is the sum of thepriceof everyproductbought from this machine minus the sum of thecostof all the products ever put in this machine. -
slots: Slot[]– this array models the array of slots in theVendingMachine.
The UML Class Diagram¶
The following UML class diagram provides an overview of the attributes and methods in this class.
classDiagram
class VendingMachine {
- totalProfit : double$
- machineProfit: double
- slots: Slot[]
+ getTotalProfit() double$
+ resetTotalProfit()$
+ VendingMachine(types: ProductType[])
+ load(slotNum: int, count: int, product: Product)
+ nextProduct(slotNum: int) Product
+ buy(slotNum: int) boolean
+ buy(type: ProductType) boolean
+ getSlotCount() int
+ getMachineProfit() double
+ toString() String
}
UPDATE 9/30: Small correction to getTotalProfit() - the return type should be a double, not an int.
Detailed Design Specifications¶
In addition to the specifications contained in the UML class diagram, this class must conform to the following specifications.
-
VendingMachine(ProductType[] types)Creates a new VendingMachine with one slot for each ProductType in the given array. Each slot is initialized using the corresponding ProductType at the same index. Thus, the number of slots equals the array length. -
load(int slotNum, int count, Product product)Loads theslotindicated byslotNumwithproductuntil it is full or untilcountis reached. Makes appropriate adjustments tomachineProfitandtotalProfitby subtracting costs from profit values.- Watch out! Recall that the
load()method of theSlotclass may throw an exception. You do not need to handle the exception here; instead, just use thethrowskeyword in the method signature to indicate that this method will "pass on" any exceptions to its caller.
- Watch out! Recall that the
-
nextProduct(int slotNum)Returns a reference to the next availableproductin the indicatedslotornullif theslotis empty. -
buy(int slotNum)Models buying one item from theslotnumber indicated. Makes appropriate adjustments tomachineProfitandtotalProfitby adding thepriceto the profit values. Returnsfalseif there is no product to buy. -
buy(ProductType type)Models buying one item from the firstslotthat matches the indicated product type. Makes appropriate adjustments tomachineProfitandtotalProfitby adding thepriceto the profit values. Returnsfalseif there is no product to buy. -
resetTotalProfit()This method resets thetotalProfitstatic instance variable to zero. This is useful when testing to make sure that different method tests start out in a known state for the static variable so the final value can be computed without knowing the order of the test runs. -
getTotalProfit()This method is a simple getter for the statictotalProfitvariable, which must track the total profit accumulated across ALL instances ofVendingMachine, not just this specific one. -
getSlotCount()Returns the number of slots in the vending machine. Note that, internally, the vending machine stores its slots in an array, where each slot can hold a specific product and its quantity. This method returns the length of that array. -
getMachineProfit()Returns the total profit earned by a single vending machine, calculated as the sum of profit for all sold products. -
toString()Returns a String representing theVendingMachine, each loaded slot, themachineProfitandtotalProfitexactly as shown below for a 2-slots with different numbers of loaded products.VendingMachinefilled with 7 Skittles in one slot and 5 Oreos in second, where nothing has been bought (so the profits are negative).
Vending Machine
SlotCount: 7 of
CANDY: Skittles Cost: 0.40 Price: 1.25.
CANDY: Skittles Cost: 0.40 Price: 1.25.
CANDY: Skittles Cost: 0.40 Price: 1.25.
CANDY: Skittles Cost: 0.40 Price: 1.25.
CANDY: Skittles Cost: 0.40 Price: 1.25.
CANDY: Skittles Cost: 0.40 Price: 1.25.
CANDY: Skittles Cost: 0.40 Price: 1.25.
SlotCount: 5 of
COOKIES: Oreos Cost: 0.85 Price: 2.25.
COOKIES: Oreos Cost: 0.85 Price: 2.25.
COOKIES: Oreos Cost: 0.85 Price: 2.25.
COOKIES: Oreos Cost: 0.85 Price: 2.25.
COOKIES: Oreos Cost: 0.85 Price: 2.25.
Total Profit: -7.05 Machine Profit: -7.05.
Hint
Use the existing toString() methods.
The Simulator Class¶
The simulator will provide a means of simulating a small business of vending machines. The vending machines of the business are stored in an array list. Vending machines can be added to the list using the provided method to simulate the growth of a business. Simulation of the business "running" and selling product is done by simulating a specific number of products being bought from every slot of every vending machine in the business and returning the totalProfit of all of the vending machines in cents.
vmList: ArrayList– models the list of vending machines owned by the company
The UML Class Diagram¶
The following UML class diagram provides an overview of the attributes and methods in this class.
classDiagram
class Simulator {
- vmList: ArrayList<VendingMachine>
+ Simulator(vmList: ArrayList<VendingMachine>)
+ addVM(vm: VendingMachine)
+ simulate(pCount: int) double
}
Detailed Design Specifications¶
-
Simulator(ArrayList vmList)instantiates aSimulator. -
addVM(VendingMachine vm)adds theVendingMachineindicated byvmto the end of the list of vending machines owned by the company. -
simulate(int pCount)simulates buyingpCountproducts from everyslotof everyVendingMachineowned by the company. Returns thetotalProfitof all of theVendingMachines.
Provided Test Files¶
Submission¶
You must submit (using Gradescope):
- Your implementation of the
ProductTypeenum, theProductclass, theSlotclass, theVendingMachineclass and theSimulatorclass. Include two tests filesSlotTest.javaandVendingMachineTest.java.
There is no limit on the number of submissions. Note that your submission will not be graded if it does not comply with the specifications. So, your submission should include a stubbed-out version of all of the classes. (This will allow you to get credit for the classes/methods that you do implement correctly.)
Grading¶
Your code will first be graded by Gradescope and then by the Professor. The grade you receive from Gradescope is the maximum grade that you can receive on the assignment
Gradescope Grading¶
Your code must compile (in Gradescope, this will be indicated in the section on "Does your code compile?") and all class names and method signatures must comply with the specifications (in Gradescope, this will be indicated in the section on "Do your class names, method signatures, etc. comply with the specifications?") for you to receive any points on this assignment. Gradescope will then grade your submission as follows:
| Criterion | Points | Details |
|---|---|---|
| Conformance to the Style Guide (Style) | 0 points | (All or Nothing; Success Required) |
| Passing Your Tests (SelfTests) | 20 points | (Partial Credit Possible) |
| Correctness (Official Tests) | 80 points | (Partial Credit Possible) |
Gradescope will provide you with hints, but may not completely identify the defects in your submission.
Manual Grading¶
After the due date, the Professor may manually review your code. At this time, points may be deducted for inelegant code, inappropriate variable names, bad comments, etc.
Recommended Process¶
Since nobody will be looking over your shoulder, you can use any process that you would like to use. However, it is strongly recommended that you use the process described here.
Step 1: Get Started¶
- Read the Full Assignment Don’t skip anything. Understanding the whole picture now will save you from headaches later.
- Break out the UML diagram to help visualize how the classes are designed and how they connect. It’s like a map for your code adventure!
- Inside
...src/hws, make a new folder calledhw3. This will be the home base for everything in this assignment. -
Create These Java Files in folder
hw3:ProductType.java,Product.java,Slot.java,VendingMachine.java,Simulator.java -
Download ProductTest.java and SimulatorTest.java into the same
hw3folder.
Step 2: Write the Simple Enums¶
- Create an enum
ProductTypewith constants like WATER, CANDY, etc. - Refer to the UML and notes to associate each constant with a cost and price.
Step 3: Stub-out the Classes¶
- Create a version of the
Product.java,Slot.java,VendingMachine.javaandSimulator.javaclasses that contains all of the methods (with appropriate signatures), each of which should return the appropriate default types. - Add the Javadoc comments to the Product class and the methods in it. Refer to javadoc comments documentation as needed.
- Get your code compiling cleanly. No red squiggles allowed.
Step 4: Understand the Test Cases¶
- Start by reading through the test cases in ProductTest.java and SimulatorTest.java. Why?💡 Because:
- It’ll give you a solid understanding of the problem, how the relevant classes are designed, how they interact, and how their methods are used.
- You’ll soon be writing your very own JUnit test files:
SlotTest.javaandVendingMachineTest.java. Knowing how the existing tests work will make your job way easier (and your tests way better).
Step 5: Implement and Test the Product Class¶
- Before you implement the logic and run anything, grab a pencil and paper and manually calculate what the expected outputs of ProductTest should be. This will help you truly understand how the Product class is supposed to behave.
- Now write the actual logic in your Product class methods based on what you’ve learned. Make it do what it’s supposed to do!
- Run
ProductTest. If any tests fail, dive into the relevant method(s) in Product.java and figure out what’s going wrong. Here is how to run tests in VSCode! - Fix the bug, run the test again, and keep iterating until all tests in
ProductTestpass.
Awesome
You’re stepping right into the world of Test-Driven Development (TDD). 🙌🏽
Step 7: Write the tests for SlotTest Class¶
- Before writing any tests, make sure you fully understand what the Slot class is supposed to do. Look at its methods, behavior, and interactions with other classes.
Think
What happens when a slot is empty?
What if you try to add a product type that is a mismatch to the slot?
How should it behave after dispensing an item?
These are some of the cases to guide your test writing.
-
In your
hw3folder, create a new file,SlotTest.java. UseProductTestas a reference template to start setting up test methods. -
For each method in Slot, write a corresponding test:
- Test expected outcomes
- Include edge cases (e.g. max and min, null values, etc.)
- Use assertEquals, assertTrue, etc., to verify behavior
Step 8: Implement and Test the Slot Class¶
- Start coding each method according to the design.
- Run SlotTest and check the results.
- If any test fails, inspect both your test logic and
Slotclass implementation to find the bug. - Tweak your code or tests as needed, then rerun everything until all tests pass and you're confident it works.
Repeat steps 7 and 8 for VendingMachine and step 8 for Simulator which can be tested using the provided SimulatorTest file.
Questions to Think About¶
You don't have to submit your answers to these questions, but you should try to answer them because they will help you determine whether or not you understand some of the important concepts covered in this assignment.
- How does using the ProductType enum improve the design of the Product class compared to using Strings or integers?
- What is the benefit of separating the logic into multiple classes like Product, Slot, and VendingMachine instead of putting all code into one class?
- What rule should setCustomPrice() in Product enforce so that the assigned price always aligns with the ROUND_PRICE and stays consistent with system pricing rules?
- How can load() method safely prevent loading incorrect product type in Slot?
- How do the buy() methods relate to machineProfit and totalProfit in VendingMachine?
- Could you reuse the VendingMachine class in a different project? Why and how or why not?
- When writing tests for Slot and VendingMachine, what helped you better understand how the classes were supposed to behave?