I'm doing some exercises in OOP and I just created a program that calculates amounts of denominations in a change at the shop. So, let's say something costs 10, I give 25.30 (for some reason), so I should get as a change: one 10, one 5, one 0.20, one 0.10.
When I started to create the program, I thought it would be nice if client of the ChangeCalculator had an option to specify his way of calculating the amounts - for example to use his own currency. That's why I created interfaces.
My program consists of:
- IChangeCalculator - represents change calculator
- ChangeCalculatorPLN - implementation that returns MoneyAmountPLN (money denominations amounts in PLN currency)
- IMoneyAmount - represents data about amounts of denominations
- MoneyAmountPLN - represents money denominations amounts in PLN
- IMoneyToCoinsConverter - represents converter that converts given money to something that implements IMoneyAmount
- MoneyToCoinsConverterPLN - converts given amount of money to MoneyAmountPLN
Here is the source code:
public interface IChangeCalculator<T> where T : IMoneyAmount
{
T Calculate(decimal price, decimal payedAmount);
}
public class ChangeCalculatorPLN : IChangeCalculator<MoneyAmountPLN>
{
IMoneyToCoinsConverter<MoneyAmountPLN> _moneyToCoinsConverter;
public ChangeCalculatorPLN( IMoneyToCoinsConverter<MoneyAmountPLN> moneyToCoinsConverter )
{
_moneyToCoinsConverter = moneyToCoinsConverter;
}
public MoneyAmountPLN Calculate(decimal price, decimal payedAmount)
{
var change = payedAmount - price;
if (change < 0)
throw new Exception("The given amount of money is not enough!");
if (change == 0)
return MoneyAmountPLN.Empty;
var amount = GetAmount(change);
return amount;
}
private MoneyAmountPLN GetAmount(decimal change)
{
return _moneyToCoinsConverter.Convert(change);
}
}
public interface IMoneyAmount
{
IEnumerable<int> GetAmounts( );
}
//MoneyAmountPLN is quite long, because it contains fields for each denomination: 500, 200, 100, 50, 20, 10, 1, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01. I'll not put it here.
public interface IMoneyToCoinsConverter<T> where T : IMoneyAmount
{
T Convert(decimal money);
}
public class MoneyToCoinsConverterPLN : IMoneyToCoinsConverter<MoneyAmountPLN>
{
public MoneyAmountPLN Convert(decimal money)
{
//logic doesn't really matter here. It returns MoneyAmountPLN with corrects amounts of denominations.
}
}
The program works, I did some unit tests. My real issue is that I'm not sure if this implementation is a good one. Did I do some mistakes? Could some parts be done better? Personally I think that maybe the structure is a little too complicated? If I saw a program like this for the first time, I would probably feel lost, but I'm not a very good programmer, so I can't say if my feeling is right. Adding new currency requires creation of new classes that implement: IChangeCalculator, IMoneyAmount, IMoneyToCoinsConverter. I think that this is too much. I think that ChangeCalculator shouldn't need to be reimplemented for each new currency, but I didn't know how to do that, since each currency requires different IMoneyAmount implementation. Calculate method of ChangeCalculator needs to know what type of IMoneyAmount it returns.
Please provide constructive feedback, it would be great to hear any of your advices.
(count, denomination)
pairs should be sufficient. Why would you need to create different types for different currencies? \$\endgroup\$