Blazor by Example - A spot of refactoring
February 10, 2020 · 7 minute read · Tags: blazor
Here’s where we got to last time in building a traffic light using Blazor.
TrafficLight.razor.cs
public class TrafficLightBase : ComponentBase
{
private State _currentState = State.Stop;
protected TrafficLightState Lights =>
TrafficLightState.Resolve(_currentState);
public void Toggle()
{
_currentState = _currentState switch
{
State.Stop => State.GetReadyToGo,
State.GetReadyToGo => State.Go,
State.Go => State.GetReadyToStop,
State.GetReadyToStop => State.Stop,
_ => _currentState
};
}
}
_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
.
Death 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.
switch(shape){
case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
}
(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.
switch(shape){
case Square s when s.Side == 0;
case Circle c when c.Radius == 0;
return 0;
case Square s when:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
}
Finally, we can combine this with the new switch
expression syntax…
var result = shape switch
{
Square s when s.Side == 0 => 0,
Circle c when c.Radius == 0 => 0,
Square s => s.Side * s.Side,
Circle c => c.Radius * c.Radius * Math.PI,
}
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…
Square s when s.Side == 0 => 0,
Circle c when c.Radius == 0 => 0,
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).
public class StopState : ITrafficLightState
{
public bool RedOn { get; } = true;
public bool AmberOn { get; } = false;
public bool GreenOn { get; } = false;
}
public class GetReadyToGoState : ITrafficLightState
{
public bool RedOn { get; } = true;
public bool AmberOn { get; } = true;
public bool GreenOn { get; } = false;
}
public class GoState : ITrafficLightState
{
public bool RedOn { get; } = false;
public bool AmberOn { get; } = false;
public bool GreenOn { get; } = true;
}
public class GetReadyToStopState : ITrafficLightState
{
public bool RedOn { get; } = false;
public bool AmberOn { get; } = true;
public bool GreenOn { get; } = false;
}
Note they all implement the same interface…
public interface ITrafficLightState
{
bool RedOn { get; }
bool AmberOn { get; }
bool GreenOn { get; }
}
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
public class TrafficLightBase : ComponentBase
{
protected ITrafficLightState Lights { get; set; } = new StopState();
protected void Toggle()
{
Lights = Lights switch
{
StopState _ => new GetReadyToGoState(),
GetReadyToGoState _ => new GoState(),
GoState _ => new GetReadyToStopState(),
GetReadyToStopState _ => new StopState(),
_ => throw new ArgumentOutOfRangeException(nameof(Lights))
};
}
}
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…
StopState s => new GetReadyToGoState(),
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.