LinkedIn
Проекты
Advertisement
RSS
разработка сайтов
оптимизация сайта
веб-дизайн
продвижение сайтов
ASP.NET
ASP.NET MVC
.NET Core
HTML5
SEO
CSS3
jQuery
Bootstrap
Angular
React
Always will be ready notify the world about expectations as easy as possible: job change page

How to stop using If-else and make your code more readable

Created: 28 Sen 2022
Author: Edis Nezir
Source: https://itnext.io/how-to-stop-using-if-else-and-make-your-code-more-readable-9d1cd97c68bf
Views: 488

Identify if-else statements as a problem in your code.

If-else statements can be problematic if not used correctly. They can be difficult to read and can lead to code that is difficult to maintain. When used incorrectly, if-else statements can also lead to errors. Especially else statements may work unexpectedly when someone else adds another feature that is not fit your if condition. No one wants an application that is not stable or works unexpectedly. Let’s examine the following example that may not be the best example to explain the if/else nested problem. But hopefully, it’ll give you a good guideline as to what the problem is.

const MyButton = ({ theme, rounded, hover, animation, content }) => {
  let className = '';                
  if (theme === 'default') {
    className = rounded ? 'default-btn rounded' : 'default-btn';
    if (hover) {
      className = className + ' hover';
    }
  } else if (theme === 'primary') {
    if (rounded) {
      if (hover) {
        if (animation) {
           className = 'primary-btn rounded hover my-custom-animation';
        } else {
          className = 'primary-btn rounded hover';
        }
      } else {
        className = 'primary-btn rounded';
      }
    } else {
      if (hover) {
        className = 'primary-btn hover';
      } else {
        className = 'primary-btn';
      }
    }
  }

  return (
    <button className={className}>{content}</button>
  );
}

It is very hard to read and understand, isn’t it? What about adding a new condition or modifying the existing one?

I am pretty sure the code above won’t work as you expect after debugging hundred times. Moreover, it would be very painful to make a simple change in such code because you must understand almost all the logic whenever you want to do changes.

⚠ Problem

Imagine that you’re creating an order management application and you run a transportation logic whenever an order is created.

void CreateOrder(Order order)
{
    /*
     *  Saving order process..  
     */    
     
     /*
     * Transportation proces..
     */
     
     TransportOrder(order);    
}

Basically, your code will be similar to the code above.

After a while, your app becomes pretty popular. Each day you receive dozens of orders from other countries and your company makes an agreement with an airways transportation company. So you should implement airways transportation to your code and use it when an order’s distance is more than 1000km.

void CreateOrder(Order order)
{
    /*
     *  Saving order process..  
     */
     
     /*
     * Transportation proces..
     */
     
     if(order.Distance < 1000) {
        TransportOrderByHighway(order);
     } else {
        TransportOrderByAirway(order);
     }
}

Then they realized that this process is expensive if an order’s weight is greater than 100kg and made another agreement with a sea transportation company but if an order is urgent the order should be transported by airway in all conditions.

void CreateOrder(Order order)
{
    /*
     *  Saving order process..  
     */   
     
     if(order.Distance < 1000 && !order.Urgent) {
        TransportOrderByHighway(order);
     } else if(order.Urgent || (order.Distance >= 1000 && order.Weight <= 100)) {
        TransportOrderByAirway(order);
     } else {
        TransportOrderBySeaway(order);
     }
}

The problem is that you modified previous conditions which absolutely breaks the open-closed principle, some extra condition blocks were added and what will happen when you want to use a railway or add some extra rules? It will probably turn into code like the first example.

✔️ Solution

Let’s start with creating a new dotnet console project with the command written below.

dotnet new console -n order-management

The project will be available on github at last of this blog.

Then, let’s create a pretty simple Order class that fits our problem.

public class Order
{
    public int Distance { get; set; }
    public bool Urgent { get; set; }
}

We need to create an abstract class (or you can use interfaces and DI with some other approaches) that contains our abstract methods.

public abstract class Transporter : IDisposable
{
    public abstract void Transport(Order order);

    public void Dispose()
    {
        // Suppress finalization.
        GC.SuppressFinalize(this);
    }
}

Now, we have an abstract Transporter class that has a transport method which accepts Order as a parameter. The next step is creating and implementing our business logic to SeaTransporter, HighwayTransporter, and AirwayTransporter classes.

public class SeawayTransporter : Transporter
{
    public override void Transport(Order order)
    {
        Console.WriteLine($"The order transported by SeawayTransporter");
    }
}

public class AirwayTransporter : Transporter
{
    public override void Transport(Order order)
    {
        Console.WriteLine($"The order transported by AirwayTransporter");
    }
}

public class HighwayTransporter : Transporter
{
    public override void Transport(Order order)
    {
        Console.WriteLine($"The order transported by HighwayTransporter");
    }
}

We have three separate classes derived from Transporter and the magic starts exactly at this point. Let’s add an abstract IsSuitableWithOrder method that returns a boolean value and implement conditions to that three classes.

Our code should be similar to the one below at this point.

public abstract class Transporter : IDisposable
{
    public abstract void Transport(Order order);
    protected abstract bool IsSuitableWithOrder(Order order);

    public void Dispose()
    {
        // Suppress finalization.
        GC.SuppressFinalize(this);
    }
}

public class SeawayTransporter : Transporter
{
    protected override bool IsSuitableWithOrder(Order order)
    {
        return !order.Urgent && (order.Distance >= 1000 && order.Weight > 100);
    }

    public override void Transport(Order order)
    {
        Console.WriteLine($"The order transported by SeawayTransporter");
    }
}

public class AirwayTransporter : Transporter
{
    protected override bool IsSuitableWithOrder(Order order)
    {
        return order.Urgent || (order.Distance >= 1000 && order.Weight <= 100);
    }

    public override void Transport(Order order)
    {
        Console.WriteLine($"The order transported by AirwayTransporter");
    }
}

public class HighwayTransporter : Transporter
{
    protected override bool IsSuitableWithOrder(Order order)
    {
        return !order.Urgent && order.Distance < 1000;
    }

    public override void Transport(Order order)
    {
        Console.WriteLine($"The order transported by HighwayTransporter");
    }
}

So, we need to use the Transporter class as a factory that returns a suitable transporter class to us but also it must do this without any hardcoded conditions or manual checks in order to be able to add new classes in the feature without modifying previous codes and reflection may help us for doing this.

Let’s start with writing an extension method that returns all derived classes from the given type.

public static class Extensions
{
    public static IEnumerable<Type> FindSubClasses(this Type baseType)
    {
        var assembly = baseType.Assembly;

        return assembly.GetTypes().Where(t => t.IsSubclassOf(baseType));
    }
}

What we want to do is find all transporter subclasses, execute their IsSuitableWithOrder method and return the suitable one. So let’s do final touches on our abstract Transporter class.

public abstract class Transporter : IDisposable
{
    public static Transporter GetTransporter(Order order)
    {
        var instance = GetSuitableInstance(typeof(Transporter).FindSubClasses(), order);
        return instance;
    }

    private static Transporter GetSuitableInstance(IEnumerable<Type> types, Order order)
    {
        foreach (var @class in types)
        {
            try
            {
                var instance = Activator.CreateInstance(@class) as Transporter;
                var isSuitable = instance.IsSuitableWithOrder(order);

                if (isSuitable != true)
                {
                    instance.Dispose();
                    continue;
                }

                return instance;
            }
            catch (System.Exception ex)
            {
                continue;
            }
        }

        throw new NotImplementedException("System can not found any transporter for given order." + order);
    }

    public abstract void Transport(Order order);
    protected abstract bool IsSuitableWithOrder(Order order);

    public void Dispose()
    {
        // Suppress finalization.
        GC.SuppressFinalize(this);
    }
}

As you see we added a static GetTransporter method that gets all subclasses from assembly and returns one of them that is suitable for the order.

Note that the factory method doesn’t have to create new instances all the time. It can also return existing objects from a cache, an object pool, or another source.

It might be useful if you check the builder pattern and method chaining.

As a final step let's return to the Program.cs file, prepare a list of orders, and transport them.

List<Order> orders = new()
{
    new Order
    {
        Urgent = false,
        Distance = 100,
        Weight = 50
    },
    new Order
    {
        Urgent = false,
        Distance = 2000,
        Weight = 4000
    },
    new Order
    {
        Urgent = false,
        Distance = 1100,
        Weight = 5
    },
    new Order
    {
        Urgent = true,
        Distance = 1200,
        Weight = 250
    }
};

foreach (var order in orders)
{
    Console.WriteLine("----------------------");
    Console.WriteLine(order.ToString());
    Transporter.GetTransporter(order).Transport();
    Thread.Sleep(200);
}

Instead of a lot of if conditions all you need is to write the code below.

Transporter.GetTransporter(order).Transport();

Moreover, you can easily add new classes for other options and add new features to existing classes.

And here is the result.

GitHub: https://github.com/edisnezir/factory-pattern-order

Similar
7 Jun 2021
Author: Himanshu Sheth
One of the most challenging things to do is ‘making the right choice.’ Arriving at a decision becomes even more complicated when there are multiple options in front of you☺. The same is the case with choosing a testing framework...
22 Aug 2021
The following are a set of best practices for using the HttpClient object in .NET Core when communicating with web APIs. Note that probably not all practices would be recommended in all situations. There can always be good reasons to...
7 Jul 2021
Author: Changhui Xu
C# has a feature, String Interpolation, to format strings in a flexible and readable way. The following example demonstrates the way how we usually output a string when we have data beforehand then pass data to the template string.var name...
19 Nov 2020
We will demonstrate how to setup Elasticsearch and learn how to write basics statements. You will learn about the structure, commands, tools in Elasticsearch API and get it up and running using standard settings.IntroductionWe will be talking about the basics...
Send message
Email
Your name
*Message


© 1999–2022 WebDynamics
1980–... Sergey Drozdov
Area of interests: .NET | .NET Core | C# | ASP.NET | Windows Forms | WPF | Windows Phone | HTML5 | CSS3 | jQuery | AJAX | MS SQL Server | Transact-SQL | ADO.NET | Entity Framework | IIS | OOP | OOA | OOD | WCF | WPF | MSMQ | MVC | MVP | MVVM | Design Patterns | Enterprise Architecture | Scrum | Kanban