Blazor by Example - A spot of refactoring
Here’s where we got to last time in building a traffic light using Blazor.
TrafficLight.razor.cs
_currentState
refers to an enum.
Every time Toggle
is invoked (by clicking a button) _currentState
is checked to see what state should come next.
Then, the component re-renders, at which point it will figure out which lights to show by calling TrafficLightState.Resolve
, passing in the value of _currentState
.
Legacy .NET web apps causing you grief?
Build modern, reliable web applications, faster with .NET and Blazor.
Build better .NET web appsDeath to the enum#
The enum here feels a little disconnected from our business logic. We’re having to maintain its current value in a field, just so we can figure out:
- What state we’re currently in
- What state should come next
But this feels like core business logic, and potentially something which we shouldn’t have to keep track of in addition to the traffic light state object itself.
One way to remove an enum, is to make better use of objects, opening the doors to employ polymorphism to trigger different behaviours.
In this case the question is, if we ditch the enum, how do we know which state we’re looking at? (and which one comes next?)
One way, is to use C#‘s new pattern matching capabilities.
A word on pattern matching with C##
Pattern matching is all about testing the “shape” of a value.
If it’s this shape, do one thing, if it’s that shape, do something else.
As humans we pattern match all the time. When you look at a banana you recognise it’s shape and appearance, and you know how to peel it.
You know it’s not an apple, but if it were an apple, you’d recognise that and be able to apply the correct peeling technique to that too.
In practice, if
and switch
statements are pattern matching algorithms; the newest versions of C# offer some new tools to make pattern matching easier/more flexible.
Specifically, it’s now possible to both check for the type of something, and get a reference to the object as that type at the same time.
(see the official docs for this example in more detail)
Before you would have had to check the type, then cast the object to that type; this approach condenses those two steps into one line in the switch statement.
Also (and we won’t use this here, but it’s useful to know) you can use when
clauses to further adjust your logic based on specific aspects of the object in question.
Finally, we can combine this with the new switch
expression syntax…
One downside here seems to be that you can’t easily express multiple tests resulting in the same answer. Our switch statement above had tests for c.Radius
and s.Side
both resulting in the answer being returned as 0
.
Alas, in the switch expression we have to write those both out as two separate test/results…
A strategy for removing the enum#
So with these pattern matching tools available to us, my plan is to represent the different traffic light states as specific types.
We can then pattern match on these types to establish what the new state should be when we attempt to cycle the traffic light to its next state.
First up, we need to create different classes to represent the possible states of our traffic light (doing away with TrafficLightState
which we had previously).
Note they all implement the same interface…
Now, we can use pattern matching on the type, to figure out which one we currently have, and which should come next…
TrafficLight.razor.cs
Incidentally, the _
here is a discard variable. It’s a temporary, dummy variable that can’t be used anywhere else in your code. We could have used regular variables here…
But because we’re not actually using that assigned variable anywhere, it’s neater in this case to use the discard pattern. That way, any attempt to retrieve its value or assign a value to it will generate a compiler error. This makes explicit, we don’t care about the value.
Now, our Lights
property starts off with an instance of StopState
.
When we invoke the Toggle
method, we use C# pattern matching (and the new switch expression syntax) to determine the type of the current value of Lights
.
For example, if the type is StopState
(which it will be when we first render the component) we’ll instantiate an instance of GetReadyToGoState
and assign that to Lights
.
At this point, because our markup is bound to Lights
the UI will be updated accordingly.
What do we gain?#
So this works, but do we gain anything, or does it needlessly add complication to the code?
Well, now we’re using objects, we can implement different behaviour for each of our traffic light states by updating the relevant class.
We can also add new states quite easily, by declaring a new implementation of ITrafficLightState
and amending our switch expression accordingly.
In that sense, we are adhering to the “Open Closed Principle” in that we can create new classes and drop them in, without having to modify any of the other implementations.
We’re also storing this state in one place Lights
, without needing a separate field (our enum value) to figure out what state we’re in.
So overall, it feels like we’re gaining more than we lose.
Check out the complete source code for the refactored traffic light here.
Legacy .NET web apps causing you grief?
Build modern, reliable web applications, faster with .NET and Blazor.
Build better .NET web apps