I need to create a simple interactive single page application which allows a user to experiment with different financial scenarios. There are several variables which can be changed in the web page and the calculation should be re-computed.
I want to model the calculation as a series of smaller calculations and the final answer should be able to be computed with a single call to a compute
method which cascades down through the calculation graph. Finally, if any of the sub-calculations are re-computed I want the possibility of other objects to be notified.
Having decided that the simplest calculation would consist of two inputs I have come up with this class:
public class TwoPredecessorCalculation { public Func<decimal> Predecessor1 { get; set; } public Func<decimal> Predecessor2 { get; set; } public Func<decimal, decimal, decimal> Calculation { get; set; } public TwoPredecessorCalculation() { } public TwoPredecessorCalculation(Func<decimal, decimal, decimal> calculation) { Calculation = calculation; } public decimal Compute() { decimal computedValue = Calculation(Predecessor1(), Predecessor2()); ValueComputed?.Invoke(this, new ComputeEventArgs{ ComputedValue = computedValue }); return computedValue; } public decimal Compute(Func<decimal> input1, Func<decimal> input2, Func<decimal, decimal, decimal> calculation) { Predecessor1 = input1; Predecessor2 = input2; Calculation = calculation; return Compute(); } public event EventHandler<ComputeEventArgs> ValueComputed; }
Calculations can then be set up so:
Func<decimal> myHourlyRate = () => 15.00m; Func<decimal> hoursWorked = () => 20m; TwoPredecessorCalculation myNetCharge = new TwoPredecessorCalculation((mhr, hw) => mhr * hw); myNetCharge.Predecessor1 = myHourlyRate; myNetCharge.Predecessor2 = hoursWorked; Func<decimal> vatRate = () => 23m; TwoPredecessorCalculation myGrossCharge = new TwoPredecessorCalculation((mnc, vatr) => mnc + (mnc * (vatr / 100))); myGrossCharge.Predecessor1 = myNetCharge.Compute; myGrossCharge.Predecessor2 = vatRate; myGrossCharge.Compute().Should().Be(369m); myNetCharge.Predecessor1 = () => 15.75m; myGrossCharge.Compute().Should().Be(387.45m);
I think this is nice simple and somewhat fluent. I do realize that this kind of thing would be even better in F#.