322 lines
No EOL
9.6 KiB
Text
322 lines
No EOL
9.6 KiB
Text
:sectnums:
|
|
:nofooter:
|
|
:toc: left
|
|
:icons: font
|
|
:data-uri:
|
|
:source-highlighter: highlightjs
|
|
:stem: latexmath
|
|
|
|
= OOB.05 -- Coffee Vending Machine
|
|
|
|
The goal of this assignment is to implement a vending machine for coffee and similar beverages like we have them at school.
|
|
|
|
NOTE: Look carefully at the following diagram to better understand which state & behaviour is `readonly`, `private` etc. This will help you implement the application.
|
|
|
|
[plantuml,id=class_diagram]
|
|
----
|
|
@startuml
|
|
hide empty methods
|
|
|
|
enum CoinType
|
|
{
|
|
Cent05
|
|
Cent10
|
|
Cent20
|
|
Cent50
|
|
Euro01
|
|
Euro02
|
|
}
|
|
class Product
|
|
{
|
|
+int FallbackPrice [const]
|
|
-int _price [readonly]
|
|
-int _stock
|
|
+bool InStock [readonly]
|
|
+string Name [readonly]
|
|
+int NumberSold [private set]
|
|
+int Price [private init]
|
|
|
|
+Product(string, int, int)
|
|
+bool AddSale()
|
|
+string ToString() [override]
|
|
}
|
|
class CoinDepot
|
|
{
|
|
+CoinType Coin [readonly]
|
|
+int Count [private set]
|
|
|
|
+CoinDepot(CoinType, int [default 0])
|
|
+CoinDepot(CoinDepot)
|
|
+void Add()
|
|
+void Clear()
|
|
+string ToString() [override]
|
|
+bool Withdraw()
|
|
}
|
|
class CoffeeVendingMachine
|
|
{
|
|
{static} -CoinType[] withdrawalOrder [readonly]
|
|
-CoinDepot[] _currentChangeCoins [readonly]
|
|
-CoinDepot[] _currentInputCoins [readonly]
|
|
-Product[] _products [readonly]
|
|
+string Location [readonly]
|
|
+int TotalChangeAmountInMachine [readonly]
|
|
+int TotalMoneyCurrentlyInput [readonly]
|
|
+Product[] AvailableProducts [readonly]
|
|
|
|
+CoffeeVendingMachine(CoinDepot[], Product[], string)
|
|
+CoffeeVendingMachine(string)
|
|
+CoinDepot[] Cancel()
|
|
+bool InsertCoin(CoinType)
|
|
+bool SelectProduct(string, out CoinDepot[]?, out int?)
|
|
+string ToString() [override]
|
|
-void AddInputToChange()
|
|
{static} -CoinDepot[] CreateCoinDepots(int)
|
|
{static} -Product[] CreateDefaultProducts()
|
|
{static} -CoinDepot? GetDepotByType(CoinDepot[], CoinType)
|
|
-int? PrepareChangeCoins(int, out CoinDepot[])
|
|
{static} -int SumDepotValue(CoinDepot[])
|
|
-bool TryFindProduct(string, out Product?)
|
|
}
|
|
|
|
CoinDepot "n" -l- "1" CoinType: contains
|
|
CoinDepot "12" -- "1" CoffeeVendingMachine: has
|
|
Product "n" --r "1" CoffeeVendingMachine: offers
|
|
|
|
@enduml
|
|
----
|
|
|
|
WARNING: As usual make sure to define the contract correctly or the unit tests won't work. Also, you might have to comment tests and enable them while you go to ensure your application always compiles.
|
|
|
|
== `Product`
|
|
|
|
Represents an article which is sold by the vending machine.
|
|
For example tea or coffee.
|
|
Each product is defined by a name, has a price and also keeps track of its stock.
|
|
|
|
Requirements:
|
|
|
|
* The price has to be a multiple of 5 (cents)
|
|
** If a different price is set fall back to a price of 50 (cents)
|
|
* The stock cannot become negative
|
|
|
|
=== String Representation
|
|
|
|
A `Product` can represent itself as a `string`.
|
|
|
|
* Pattern: <name> <price> [<stock> in stock | <sales> sold]
|
|
* Example: `Cappuccino € 0,85 [9 in stock | 1 sold]`
|
|
|
|
CAUTION: To ensure uniform _locale_ formatting, make sure to use the specified `CultureInfo` available via the `Culture` property of the `Const` class
|
|
|
|
== `CoinDepot`
|
|
|
|
Each coin depot holds coins of a specific type, and one type only.
|
|
It can contain none, one or more coins -- for simplicity we do not specify an upper bound.
|
|
|
|
Imagine something like this (several depots for various coins) just inside the vending machine:
|
|
|
|
image::pics/coin_dispenser.jpg[Coin Dispenser, width=400]
|
|
|
|
Requirements:
|
|
|
|
* Each depot accepts only a single type of coin
|
|
* Coins can be added to it
|
|
* Coins can be withdrawn from it *if* there are coins left in the depot
|
|
* The depot can be emptied (cleared)
|
|
* It can copy another depot
|
|
** The new one then contains the same type and amount of coins
|
|
** => Useful for some implementation details
|
|
|
|
=== `CoinType`
|
|
|
|
Each coin belongs to a specific _type_.
|
|
This `CoinType` is implemented as an `enum`.
|
|
|
|
IMPORTANT: Use a specific property of the `enum` to 'encode' the value of the coin _within_ the type.
|
|
|
|
.Values
|
|
----
|
|
Cent05: 5
|
|
Cent10: 10
|
|
Cent20: 20
|
|
Cent50: 50
|
|
Euro01: 100
|
|
Euro02: 200
|
|
----
|
|
|
|
=== String Representation
|
|
|
|
A `CoinDepot` can represent itself as a `string`.
|
|
|
|
* Pattern: <coin type> x<count>
|
|
* Example: `Cent50 x3`
|
|
|
|
== `CoffeeVendingMachine`
|
|
|
|
A `class` which simulates (omitting _a lot_ of details) such a machine:
|
|
|
|
image::pics/coffee_machine.png[Coffee Vending Machine, width=200]
|
|
|
|
This is the major part of the application so the specifications & requirements provided to you are much more exhaustive.
|
|
You'll still have to _think_ about what you are doing to successfully complete this assignment.
|
|
|
|
=== State
|
|
|
|
* When returning change money we start with the coins with the biggest denomination.
|
|
This behaviour is the same for _all_ machines, so we can store the order of coins in a `static` field.
|
|
* The array of products is set on construction
|
|
** Each product defines its own price
|
|
** It cannot be restocked
|
|
* We have _two_ arrays of coin depots
|
|
** One for the money the customer inputs into the machine
|
|
** A second one for the change money
|
|
** _The logic could also be implemented with only one set of depots, but we'll do it with two, because that's a little easier_
|
|
* It has a location
|
|
** For example '2nd floor'
|
|
* Information that can be calculated on the fly:
|
|
** How much change money is currently in the machine
|
|
*** This does _not_ include the money input by the customer _until_ a product selection has been made
|
|
** How much money did the customer put into the machine
|
|
** Which products are currently available
|
|
|
|
=== Construction
|
|
|
|
There are two ways to create an instance of `CoffeeVendingMachine`:
|
|
|
|
. Only provide a location
|
|
** This will initialize products and change money with <<default_values,default values>>
|
|
** Will utilize the _other_ constructor to avoid code duplication
|
|
. Also provide products and initial change money values
|
|
|
|
==== Default Values [[default_values]]
|
|
|
|
Use the following default values.
|
|
|
|
* For each type of coin (there are six in total) 'load' 3 coins into the change money depots
|
|
* Create the following products:
|
|
|
|
|===
|
|
|Name |Price |Stock
|
|
|
|
|Cappuccino
|
|
|0.85€
|
|
|10
|
|
|
|
|Mocca
|
|
|1.00€
|
|
|10
|
|
|
|
|Cacao
|
|
|0.60€
|
|
|10
|
|
|===
|
|
|
|
=== Behaviour
|
|
|
|
In the following find some explanation what it is supposed to do for each `public` method.
|
|
|
|
TIP: Also look at the provided unit tests! They very clearly define the expected behaviour.
|
|
|
|
.In alphabetical order
|
|
|
|
* `Cancel`
|
|
** Aborts the current sale process
|
|
** All input coins are returned
|
|
* `InsertCoin`
|
|
** A new coin is inserted by the customer
|
|
** We can only accept coins for which we have a depot
|
|
* `SelectProduct`
|
|
** Customer selects a product to buy
|
|
** Only works if they input enough money _and_ the product ist still in stock
|
|
*** We also have to notify the product that it got sold
|
|
** If they input too much money we _attempt_ to give as much change as possible
|
|
*** We can use the coins in the change money depots _and_ the coins the user put into the machine at this point
|
|
**** The remaining coins are added to the change depots for future use
|
|
*** We cannot give out more change than the customer 'deserves'
|
|
**** => If we do not have enough of use able coins (e.g. we cannot hand out a 1€ coin if we only want to hand out 20 cents) we simply keep the difference and the user looses -- we are a business after all 😉
|
|
|
|
==== Private Methods
|
|
|
|
In the <<class_diagram,class diagram>> up top several `private` methods are listed:
|
|
|
|
* These are _just a suggestion_
|
|
* There are no tests for them
|
|
** Because we test the `public` interface/contract, not the `private` implementation details
|
|
* => You don't have to implement _those_ methods
|
|
** But you'll probably need some additional, `private` methods, otherwise your `public` ones will get much too large
|
|
|
|
==== String Representation
|
|
|
|
* Meant to be printed to the terminal
|
|
** So it includes lines breaks, borders, alignment,...
|
|
** _This is not something you would usually do in a `ToString` implementation_
|
|
* Makes use of the string representations of `CoinDepot` & `Product`
|
|
* Look at the <<sample_run,sample run>> to see how the string representation of the vending machine is supposed to look.
|
|
|
|
=== Hints
|
|
|
|
Some coding tips that we already learnt and that might be useful in your implementation, but you might not have used for some time.
|
|
|
|
.You probably forgot about ranges
|
|
[source,csharp]
|
|
----
|
|
int[] numbers = [1,2,3,4,5];
|
|
var firstPart = numbers[..3]; // {1,2,3}
|
|
----
|
|
|
|
.Remember: enums are just named integers
|
|
[source,csharp]
|
|
----
|
|
var result1 = (int) Numbers.Zero + (int) Numbers.Eleven;
|
|
var result2 = (int) Numbers.One + (int) Numbers.Two;
|
|
Console.WriteLine(result1 + result2); // 14
|
|
|
|
enum Numbers {
|
|
Zero,
|
|
One = 1,
|
|
Two,
|
|
Eleven = 11
|
|
}
|
|
----
|
|
|
|
.Parameters can be optional (at the end of the parameter list)
|
|
[source,csharp]
|
|
----
|
|
Foo(2, true); // 4
|
|
Foo(2, false); // 2
|
|
Foo(2); // 2
|
|
|
|
void Foo(int aRequiredParam, bool anOptionalParam = false)
|
|
{
|
|
var result = anOptionalParam ? aRequiredParam * 2 : aRequiredParam;
|
|
Console.WriteLine(result);
|
|
}
|
|
----
|
|
|
|
== `Program`
|
|
|
|
Implement _at least_ the minimal version.
|
|
Also implement the extended version for _extra credit_.
|
|
|
|
=== Minimal Version
|
|
|
|
. Create an instance of `CoffeeVendingMachine`
|
|
. Insert some coins (enough to buy something)
|
|
. Select a product
|
|
. Print the state of the machine to the terminal
|
|
|
|
==== Sample Run [[sample_run]]
|
|
|
|
image::pics/sample_run.png[Sample Run]
|
|
|
|
=== Extended Version
|
|
|
|
Implement user interaction which allows the user to insert coins & select a product via the terminal.
|
|
You can re-use the string representation of the minimal version here to display the state of the vending machine, but you'll need additional input and output logic.
|
|
|
|
WARNING: _With_ proper retry/error handling of course
|
|
|
|
== XML Doc
|
|
|
|
Write _proper_ XML Doc for _at least_ all your `public` members.
|
|
This task is *not* optional! |