In this Chapter we will see how we can fix growing switch case which cause code maintainability issue
In our development projects we may have below situation where switch cases increase with time and getting difficult to maintain
Let’s understand problem
using System;
class Program
{
static void ProcessOrder(string orderType)
{
switch (orderType)
{
case "NEW_ORDER":
Console.WriteLine("Processing new order.");
// New order logic here
break;
case "CANCEL_ORDER":
Console.WriteLine("Cancelling order.");
// Cancel order logic here
break;
case "SHIPPING":
Console.WriteLine("Shipping order.");
// Shipping logic here
break;
case "DELIVERED":
Console.WriteLine("Order delivered.");
// Delivered logic here
break;
default:
Console.WriteLine("Unknown order type.");
break;
}
}
static void Main(string[] args)
{
ProcessOrder("NEW_ORDER");
}
}
1. Difficulty in Maintenance:
When we add new cases to a switch, we must need to update code and number of lines in single function will increase and then this will be difficult to maintain.
2. Breaking SOLID Principles:
The Single Responsibility Principle (SRP) and Open/Closed Principle (OCP) from SOLID design guidelines are difficult to follow with large switch statements. These principles state that:
A class should have one reason to change (SRP), and
A class should be open for extension, but closed for modification (OCP).
If you modify a switch statement by adding a new case, you’re violating both principles.
3. Code Quality in SonarQube
When we have such bad code and number of lines will increase this will caught by SonarQube (in devops pipeline )and will break build due to high complexity in functions. in that case we need to break this code.
4. Code Readability and Testability:
large switch statement can make the code harder to read and test. Each case inside the switch block can contain multiple lines of code, including business logic, side effects, and error handling. This complexity makes unit testing difficult because it’s harder to isolate the individual behaviors tied to each case.
Solution Using Dictionary
we will replace the switch statement with a Dictionary where each key represents an order type, and the value is a delegate or a method that handles the logic for that order type.
Consider code below
using System;
using System.Collections.Generic;
class Program
{
// Define methods for each order type
static void NewOrder()
{
Console.WriteLine("Processing new order.");
// New order logic here
}
static void CancelOrder()
{
Console.WriteLine("Cancelling order.");
// Cancel order logic here
}
static void Shipping()
{
Console.WriteLine("Shipping order.");
// Shipping logic here
}
static void Delivered()
{
Console.WriteLine("Order delivered.");
// Delivered logic here
}
static void UnknownOrder()
{
Console.WriteLine("Unknown order type.");
}
}
Here we are defining Action methods those need to execute on switch cases
Defining Dictionary Code for switch case.
static void Main(string[] args)
{
// Define a dictionary mapping order types to their corresponding methods
Dictionary<string, Action> orderActions = new Dictionary<string, Action>
{
{ "NEW_ORDER", NewOrder },
{ "CANCEL_ORDER", CancelOrder },
{ "SHIPPING", Shipping },
{ "DELIVERED", Delivered }
};
// Example Usage
ProcessOrder(orderActions, "NEW_ORDER");
ProcessOrder(orderActions, "CANCEL_ORDER");
ProcessOrder(orderActions, "UNKNOWN_ORDER");
}
static void ProcessOrder(Dictionary<string, Action> orderActions, string orderType)
{
// Check if the order type exists in the dictionary and execute the corresponding action
if (orderActions.ContainsKey(orderType))
{
orderActions[orderType](); // Call the appropriate method
}
else
{
UnknownOrder(); // Default behavior for unknown order types
}
}
Details of code
- Dictionary of Actions: A Dictionary<string, Action> is used to map order types (the string keys) to corresponding methods (the Action values). The Action delegate in C# represents a method that doesn’t return a value (i.e., a void method).
- Processing Orders: In the ProcessOrder() method, we check if the order type exists in the dictionary using ContainsKey(). If it exists, we invoke the corresponding action method. If the order type doesn’t exist, the UnknownOrder() method is called.
Benefits:
- Scalability: You can easily add new order types by simply adding new methods and updating the dictionary. No need to modify complex switch statements.
2. Readability and Maintainability: The code is more organized, and each order type is handled by a dedicated method.
I have posted another article to fix multiple If-else using Chain of responsibility pattern. have a look and enjoy
#dotnet, #csharp, #coding, #design
Happy coding Thanks
Comments
Post a Comment