Home > OS >  How can I avoid the switch (.net)
How can I avoid the switch (.net)

Time:12-09

I am currently developing an application in which there are several roles.

For a feature, I need to return information according to the role. Thus, I created a switch on the role enumeration for set it up.

public Enum Role {
  admin = 1,
  agent = 2,
  responsible = 3};


switch(value){
case Role.admin :
    //Do something
    break;
case Role.agent :
    //Do something
    break;
case Role.responsible :
    //Do something
    break}

I read in the clean code book that we should avoid having switch in our code.

So, for my case what is solution to avoid this switch.

CodePudding user response:

Such switches are usually bad in every architecture. Instead of switching on enum, use polymorphism:

interface IRole 
{
    void Do();
}

class Admin: IRole
{
    override void Do()
    {
         //do the admin thing
    }
}

class User : IRole
{
    override void Do()
    {
         //do the user thing
    }
}

Etc. This will allow you to separate functionalities today and easily add more roles in the future.

CodePudding user response:

Hopefully, your clean code book explains why you should avoid using a switch. If your enum is a small set and you have only one place where you will handle such a check, abstracting it away will most likely be overengineering and make it less readable.

You can make the code cleaner by applying SOLID principles, which would most likely lead to a GOF pattern. GOF patterns will generally help you avoid large if/else chains and bloated switch statements.

How you make this code cleaner would depend on the feature you are writing. A pattern using handlers of sorts will most likely fit the scenario. I will present you with a solution using a strategy pattern. Let's say this switch is for a UI handler of sorts, and you want to colour the text to a table showing users with these roles.

Lets say the role belongs to a user:

    public class UserEntry
    {
       public Role UserRole {get;set;}
    }

And you want to colour the role or user entry according to a selected theme:

    public interface ITheme
    {
       string GetColour(UserEntry entry);
    }
    
    public class DarkTheme : Theme
    {
       public DarkTheme() : base(new[] 
          { 
             new AdminColourizer("darkblue"), 
             new AgentColourizer("darkgreen"), 
             new ResponsibleColourizer("darkred")})
          {}
    }
    
    public class LightTheme : Theme
    {
       public LightTheme() : base(new[] 
          { 
             new AdminColourizer("lightblue"), 
             new AgentColourizer("lightgreen"), 
             new ResponsibleColourizer("lightred")}) 
          {}
    }
    
    public abstract class Theme : ITheme
    {
       public Theme(IEnumerable[] colourizers)
       {
          _colourizers = colourizers;
       }
    
       private IEnumerable<Colourizer> _colourizers 
    
       public string GetColour(UserEntry entry)
       {
          return _colourizers.SingleOrDefault(c => c.Colour(entry));
       }
    }

Then we break the switch into handlers

    public abstract class Colourizer 
    {
       public abstract class Colourizer(string colour)
       {
          this.colour = colour;
       }
    
       protected string colour;
       public abstract string Colour(UserEntry entry);
    }
    
    public class AdminColourizer : Colourizer
    {
        AdminColourizer(string colour) : base(colour) {}
    
        public override string Colour(UserEntry entry)
        {
          if (entry.UserRole == Role.Admin)
             return colour;
    
          return null;
        }
    }
    
    public class ResponsibleColourizer : Colourizer
    {
        ResponsibleColourizer(string colour) : base(colour) {}
    
        public override string Colour(UserEntry entry)
        {
          if (entry.UserRole == Role.Responsible)
             return colour;
    
          return null;
        }
    }
    
    public class AgentColourizer : Colourizer
    {
        AgentColourizer(string colour) : base(colour) {}
    
        public override string Colour(UserEntry entry)
        {
          if (entry.UserRole == Role.Agent)
             return colour;
    
          return null;
        }
    }

And we can use the handlers in the Theme strategy like so:

    public class UserTableRenderer
    {
       public UserTableRenderer(ITheme theme)
       {
          _theme = theme;
       }
    
       private ITheme _theme;
    
       public void RenderTable(IRepository respository)
       {
          var users = repository.GetUsers(); // list of UserEntry 
    
          foreach (var user in users)
          {
             WriteText(entry: user, colour: _theme.GetColour(user));
          }
       }
    
    
       public void Colour(UserEntry entry)
       {
    
       }
    
       public class MyApp()
       {
          var repo = new MyDatabaseRepo();
          var renderer = new UserTableRenderer(new DarkTheme() /* or LightTheme() */);
          renderer.RenderTable(repo);
       }

In this code, the switch statement is abstracted into the handlers called Colourizers in this case. This approach emphasizes the S (Single-responsibility) principle in SOLID as each handler applies logic concerning a single concern (i.e. role is admin -> do x).

Any modifications or extensions of handling admin will only involve a programmer meddling with the AdminColourizer and may not even encounter the other avenues of the switch statement.

  • Related