There are many arguments on the web regarding the switch-case statement. It seems that half of the programmers think that switch-case statement is actually an anti-pattern, and other half claims there are in fact use cases for this concept. Usually, the second group tries to prove a point that in some simple situations it is alright to use switch-case, like some really simple checking. My experience tells me other otherwise and I am belonging fully to the first group.
These statements in these so-called “simple situations” often get out of hands, and what we usually end up with large unreadable chunks of code. Not to mention that if the requirements change (and we know that they do), the statement itself has to be modified, thus breaking Open-Close Principle. This is especially the case in enterprise systems and this kind of thinking leads to maintenance hell.
Another problem of switch-case statements (just like if-else statements) is that they combine data and behavior, which is something that reminds us of procedural programming. What does this mean and why is it bad? Well, data in its essence is an information that doesn’t contain behavior. Treating data as behavior and using it for controlling the workflow of your application is what creates mentioned maintenance hell. For example, take a look at this code:
Data in this code is variable named data, but also data is the string that is printed on the console. The behavior is the action of printing information on the console, but also the behavior is mapping set of information from the dataset to some action, ie. mapping “Item1” to action1, “Item2” to action2, etc.
As you can see that data is something that can be changed and behavior is something that shouldn’t be changed, and mixing them together is the cause of the problem.
So, how to get rid of switch-case statements?
From Switch-Case to Object-Oriented code
Recently, I was working on the code that relied on the switch-case statement. So, I created next example based on that real-world problem and on the way I refactored it.
Here we are having entity class with the property Type. This property defines how the function GetNewValueBasedOnType will calculate its result. This class is used in this manner:
In order to modify this example into the proper object-oriented code, we need to change quite a few things. The first thing we can notice is that even though we have multiple Entity types, they are still just Entity types. Donut is a donut, no matter what flavor it is. This means that we can create a class for each entity type, and all those classes should implement one abstract class. Also, we can define an enumeration for the entity type. That looks like this:
Concrete implementations of Entity class look like this:
That is much better. Now we are closer to real object-oriented implementation, not just some fancy procedural implementation. We used all those nice concepts that object-oriented programming provides us with, like abstraction and inheritance.
Still, there is a question how to use these classes. It seems that we didn’t remove the switch-case statement, we just moved it from Entity class to the place where we will create an object of concrete Entity implementations. Basically, we still need to determine which class we need to instantiate. At this point we can make Entity Factory:
Now, we can use this code like this:
Many times I showed this code to someone, I would get a comment that this is too complicated. But, the thing is that this is the way that real object-oriented code should look like. It is more resilient to changes and its behavior is separated from the data. Data no longer control the workflow. We can see that entity type and entered value are changeable, but that they are not intervened with behavior. By doing that we increased the maintainability of the code.
Another complaint that I usually get is that this code still doesn’t fully satisfy Open Close Principle, meaning that if new entity type needs to be added, we need to change Entity Factory class itself. This is a very good complaint, and it is right on point. Nevertheless, we shouldn’t forget that code that fulfills SOLID principles is something we should always strive to, but many times we can not quite get there. In my opinion, Open Close Principle is the most unreachable of all SOLID principles.
You might ask yourself why is this chapter here. We were talking about the switch-case statement, so what does pattern matching has to do with this? Well, remember when I said that I think that switch-case shouldn’t be used? The thing is that C# 7 introduces this feature – pattern matching, which is already well established in functional programming languages, like F#. This feature heavily relies on the switch-case statement, so let’s take a moment and go bit deeper in this and see if this could change my mind.
What is pattern matching after all? To better explain this I’ll use F# example. F# has this mighty concept called discriminated union. Using this concept we can define a variable, that can have multiple types, meaning not only value is changeable but also the type is changeable too. Wait, what? Here is an example:
This means is that we have defined type Shape, that can be either Rectangle or Circle. Variable of type shape could be either of these two types. Like Schrodinger’s cat, we will not know the type of that variable until we take a closer look. Now, let’s say we want to write a function that will get the height of the shape. That would look like something like this:
What happened here is that we checked the type of the variable shape, and returned Rectangle width if the shape is of type Rectangle, or returned two times radius value if the shape is of type Circle. We have opened the box and collapsed a wavefunction.
Although this concept seems unnecessarily complicated at first, in practice it is both elegant and powerful. Controlling the workflow this gives us so many possibilities, so I was very happy when I saw that it will be a part of new C#.
So how this code would look like in C# before improvements?
What this feature giving us in C# is the ability to simplify this syntax. It is giving a little bit more usability to the switch statement too, meaning that now we can switch by the type of the variable.
Another thing that can be done now is using of when clause.
To be honest, I am not very impressed with possibilities that pattern matching in C# 7.0 provided us with. What makes this concept so powerful in F# are basically discriminated unions, and without them, pattern matching in C# is pretty limited. Also, I haven’t done any real-world project using this C# version, so I can not really be the judge of it. My impression is that, switch-case statements got a little bit charged up, and things can look little bit cleaner using them, but that they are still sort of anti-pattern. In the end, I didn’t get convinced that these statements have a valid use case and that they should be used.
What I haven’t covered in this post is Strategy pattern, which is also one can be a good choice in some situations, if you want to remove switch-case from your code.
Read more posts from the author at Rubik’s Code.