160 lines
No EOL
5.6 KiB
Text
160 lines
No EOL
5.6 KiB
Text
[](https://classroom.github.com/a/HUcMlwx_)
|
|
:sectnums:
|
|
:nofooter:
|
|
:toc: left
|
|
:icons: font
|
|
:data-uri:
|
|
:source-highlighter: highlightjs
|
|
:stem:
|
|
|
|
= Int.04 -- Shipping Costs
|
|
|
|
You are tasked with implementing a shipping cost calculation component of a webshop.
|
|
The store is based in Austria and ships to several other countries.
|
|
But the owner does not like their neighbors, so they decided to not ship to Austria 🙃
|
|
|
|
Three shipping options are available to the customers:
|
|
|
|
. Self-Pickup: come by the store and pick up your order
|
|
** Free of charge!
|
|
. Leonding Parcel Service (LPS)
|
|
** Fast, affordable, operated by always reliable students of the local HTL
|
|
. LuxuryShipping
|
|
** Boxes are only handled with single-use silk gloves by employees who attended a butler school in Switzerland
|
|
|
|
The handling fee of the shop itself, together with the actual billing of the product(s) is handled by a different component of the webshop, so you don't have to concern yourself with these matters.
|
|
|
|
You have worked out the following data model:
|
|
|
|
[plantuml]
|
|
----
|
|
@startuml
|
|
hide empty fields
|
|
hide empty methods
|
|
|
|
interface ICountryDistanceProvider {
|
|
+CountryDistanceInformation? GetDistanceTo(string countryName)
|
|
+bool IsPossibleCountry(string countryName)
|
|
}
|
|
struct CountryDistanceInformation {
|
|
+int ApproxDistance [readonly]
|
|
+bool IsDirectNeighbor [readonly]
|
|
+bool IsEuMember [readonly]
|
|
}
|
|
interface IMeasuredBox {
|
|
+int Width [readonly]
|
|
+int Height [readonly]
|
|
+int Depth [readonly]
|
|
+double Weight [readonly]
|
|
}
|
|
interface IShippingCostCalculator {
|
|
+string CarrierName [readonly]
|
|
+decimal? CalculateShippingCosts(string targetCountry, IMeasuredBox box)
|
|
}
|
|
class CountryDistanceProvider <<sealed>> {
|
|
{static} -Dictionary<string, CountryDistanceInformation> distanceDatabase [readonly]
|
|
}
|
|
class Package <<sealed>> {
|
|
-int _width [readonly]
|
|
-int _height [readonly]
|
|
-int _depth [readonly]
|
|
-double _weight [readonly]
|
|
{static} -T SanitizeValue<T>(T value) [T : INumber<T>]
|
|
}
|
|
class SelfPickup <<sealed>> {}
|
|
abstract class ShippingCostCalculatorBase {
|
|
#ICountryDistanceProvider CountryDistanceProvider [readonly]
|
|
#ShippingCostCalculatorBase(ICountryDistanceProvider countryDistanceProvider)
|
|
{static} #int SumOfLongestAndShortestSides(IMeasuredBox box)
|
|
}
|
|
class LPSShippingCostCalculator <<sealed>> {
|
|
+LPSShippingCostCalculator(ICountryDistanceProvider countryDistanceProvider)
|
|
-decimal CalcDistanceCost(string targetCountry)
|
|
}
|
|
class LuxuryShippingCostCalculator <<sealed>> {
|
|
+LuxuryShippingCostCalculator(ICountryDistanceProvider countryDistanceProvider)
|
|
}
|
|
|
|
ICountryDistanceProvider <|.. CountryDistanceProvider
|
|
IMeasuredBox <|.. Package
|
|
IShippingCostCalculator <|.. SelfPickup
|
|
IShippingCostCalculator <|.. ShippingCostCalculatorBase
|
|
ShippingCostCalculatorBase <|-- LPSShippingCostCalculator
|
|
ShippingCostCalculatorBase <|-- LuxuryShippingCostCalculator
|
|
ShippingCostCalculatorBase *-- ICountryDistanceProvider
|
|
|
|
@enduml
|
|
----
|
|
|
|
== Implementation
|
|
|
|
You have been provided with a skeleton code, unit tests and XMLDoc comments.
|
|
In addition, there is an almost complete `Program` which provides a simple UI.
|
|
Complete the application!
|
|
|
|
=== `CountryDistanceProvider`
|
|
|
|
* A _default_ country distance provider
|
|
* Contains information about all countries the store ships to
|
|
** Approximate distance (calculated as great-circle distance between the capital cities)
|
|
** If the country shares a border with Austria
|
|
** If the country is a member of the EU
|
|
* Do you feel comfortable with the dictionary initializer?
|
|
|
|
=== `Package`
|
|
|
|
* Simple implementation of `IMeasuredBox`
|
|
* Hint: the interface demands `get` properties, that does _not_ stop you from adding `init` setters
|
|
* The `SanitizeValue` method allows to ensure that numeric values are not stem:[<0]
|
|
** It can be used for both `int` & `double`
|
|
** To allow for this flexibility it is a _generic math_ method, with a type parameter constraint of `T : INumber<T>` => this ensures, that `T.Zero` exists for every `T`
|
|
|
|
=== `SelfPickup`
|
|
|
|
* A very simple shipping cost 'calculator' => self pickup is always free of charge
|
|
* The 'carrier name' is `Self Pickup`
|
|
|
|
=== `ShippingCostCalculatorBase`
|
|
|
|
* Base class for shipping providers which need to calculate shipping cost based on distance and package measurements
|
|
* `SumOfLongestAndShortestSides` determines the sum of the longest and shortest side
|
|
** Example: `120x190x340` => `460`
|
|
|
|
=== `LPSShippingCostCalculator`
|
|
|
|
* Can only ship to a country known to the distance provider
|
|
* Total shipping cost is calculated based on three parts:
|
|
.. Weight
|
|
*** Max. 20€
|
|
*** stem:[< 1.5"kg"]: 0€
|
|
*** stem:[< 2"kg"]: 0.5€
|
|
*** stem:[< 3"kg"]: 1.5€
|
|
*** stem:[>= 3"kg"]: stem:[x * 0.55]€
|
|
.. Size
|
|
*** stem:[< 250"mm"]: 0€
|
|
*** stem:[< 400"mm"]: 0.75€
|
|
*** stem:[< 650"mm"]: 1.25€
|
|
*** stem:[< 900"mm"]: 2.6€
|
|
*** stem:[>= 900"mm"]: 3.5€
|
|
.. Distance
|
|
*** Base price 2.99€
|
|
*** If _not_ an EU member +2.49€
|
|
*** Per 500km +1.1€ (added at least once)
|
|
**** If direct neighbor: 25% reduction of price per 500km
|
|
* The 'carrier name' is `Leonding Parcel Service`
|
|
|
|
=== `LuxuryShippingCostCalculator`
|
|
|
|
* Can only ship to a country known to the distance provider
|
|
* Total shipping cost is calculated as follows:
|
|
** Max. 4444€
|
|
** Box size cost (dimensions in centimeter): stem:[("height"*"width"*"depth")^2]
|
|
*** 'Länge mal Breite mal Höhe zum Quadrat'
|
|
** Total cost: stem:["km" * "boxSizeCost"]
|
|
* The 'carrier name' is `LuxuryShipping`
|
|
|
|
== Sample Run
|
|
|
|
Here a recording of a sample run of the program, including invalid input retries:
|
|
|
|
video::pics/sample_run.mp4[Sample Run, width=800px] |