In my previous article I designed and implemented a small desktop calculator using Event-Based Components. That was fun and went smoothly – but in the end I was in a hurry and missed a bug and a feature. In the meantime I found some time to fix both.
Final architecture
Let me take the opportunity to show you the application architecture in its entirety. The missing feature – Clear calculation - has already been added:
This is the high level view. All activities except one are so simple, no further detail is needed. Calculate, though, deserves some refinement. So let´s quickly zoom in:
That´s it. An application defined on two levels of abstractions. No class diagrams needed, no component diagrams, no packet diagrams. Just some kind of activity diagrams with a clear set of rules for translating them into C# code – which, by the way, you can find here.
The beauty of it – as I see it – is that you can draw EBC diagrams on as many levels of abstraction and you´ll find all of them in your code. Model and code are always in sync.
And what about classes? Sure, there are classes:
But mind you: they do not depend on each other. That´s the power of events. The classes are either manifestations of activities (e.g. Calculate) and thus do not depend on other´s by definition. An activity does not depend on another activity to be performed; it just uses input and produces output (or a side effect). Or the classes are aggregations of activities and thus also do not need to depend on others.
Bug fixed
The bug to fix was, that the calculator did not deliver a correct result if after a “=” an operator was clicked and then another number entered, e.g. 1+2=+4= did not deliver 7 as the result.
The solution to that was a bit tricky. But in the end I managed to implement it without a major architectural change. I only needed to make the current number a nullable type:
class NumberAggregator
{
private double? currentNumber;
...
public void ExtendNumber(char c, Action<double?> out_currentNumber)
{
if (c == '.')
{
...
}
else
{
if (this.currentNumber == null) this.currentNumber = 0.0;
...
}
out_currentNumber(this.currentNumber);
}
...
That way the CalculatorEngine can distinguish between an operator clicked after entering a number and an operator clicked after “=”:
public void In_ApplyOperation(Tuple<char, double?> input, Action<double> out_result)
{
double result = 0.0;
if (input.Item2 != null)
{
if (this.operation == '=') this.operands.Clear();
this.operands.Push(input.Item2.Value);
result = Calculate();
}
else
if (this.operands.Count > 0)
result = this.operands.Peek();
this.operation = input.Item1;
out_result(result);
}
It made me quite happy that no change to the overall design diagram was needed to accomodate the bug fix. I took it as a sign of a good design ;-)
Summary
Event EBC designs don´t protect you from forgetting stuff and writing buggy code. But still I think they improve my coding. They narrow my focus, because the domain stuff happens mostly in leaf activities:
Depending on your level of detail those atomic activities are comparatively simple.
Composite activities on the other hand seem complicated – but they are not. I even think they could be automatically generated from an EBC design. For now, however, I´ll stick to hand crafting my EBC code ;-)
Models
Since EBC diagrams do not show implementation details like classes, objects, interfaces they are true models to me. They abstract from a concrete platform. You could translate them to C#, Java, Ruby, F#, C++ etc. That I also find quite important. You don´t want to get stuck in implementation details during the design phase.