Conditional statements, like if-else and switch, are core to any programming language. We use them day in and day out, but it is surprising that it is viewed as a code smell in an object-oriented programming language (OOP). If you ever wondered why this is like I did, then this article is for us.

Before we go any deeper, let's understand two very important concepts of SOLID principles in OOP

Single Responsibility Principle

Robert C. Martin describes the Single responsibility principle as:

“A class should have only one reason to change.”

Martin defines a responsibility as a reason to change and concludes that a class or module should have one, and only one, reason to be changed (i.e. rewritten).

As an example, consider a module that compiles and prints a report. Imagine such a module can be changed for two reasons. First, the content of the report could change. Second, the format of the report could change. These two things change for very different causes; one substantive, and one cosmetic.

The single responsibility principle says that these two aspects of the problem are really two separate responsibilities, and should, therefore, be in separate classes or modules. It would be a bad design to couple two things that change for different reasons at different times.

The reason it is important to keep a class focused on a single concern is that it makes the class more robust. Read here for a more detailed explanation.

Open/Closed Principle

Robert C. Martin viewed this principle as “the most important principle of object-oriented design”. He described the Open/Closed Principle as:

“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”

The general approach of this principle is great. It tells you to write your code so that you will be able to add new functionality without changing the existing code. That prevents conditions in which a change to one of your classes also requires you to adapt all depending classes. Read here for a more detailed explanation.

Let’s get back to the initial question of why conditional statements are a code smell and start with a simple example.

Problem:

let’s assume the following function is one of the behaviours of a class named “Animal”.

private static String getSoundIfElseWay(String animal) {

if (animal.equalsIgnoreCase(“Dog”))

return “Bark”;

else if (animal.equalsIgnoreCase(“Cat”))

return “Mew”;

else if (animal.equalsIgnoreCase(“Lion”))

return “Roar”;

return “”;

}

Here we have a conditional block that performs various actions depending on the type of object. This now clearly violates the Single Responsibility Principle. SRP advocates that a class should have one responsibility, but it clearly violating this by owning behaviours or responsibilities of “dog”, “lion”, and “cat”.

Also, this violates the open/closed principle as well. Consider now we need to implement the sound of a “tiger”, to get this we need to add one more “else if” condition to the above function. This forces modifications to the “Animal” when it is trying to extend a behaviour making it open for modification and closed for the extension. Every time when we want to add/extend any functionality, we should go and change its source code.

Now it is apparent that conditional here is a code smell. So how do we fix it?

There are several refactoring recipes for removing conditionals from your code.

Restore Conditional With State/Strategy Restore Conditional With Polymorphism

Restore Conditionals With Polymorphism

polymorphism in a very naive translation means same name, different logic.

In most cases, when we replace conditional with polymorphism, we deal with a subtype polymorphism. This type of polymorphism in OOP means the ability to change the behaviour of the method by providing a method with the same name in a child class. Let’s consider the most known example from OOP tutorials:

abstract class Shape

{

abstract public int getArea();

}



class Square extends Shape

{

protected int length;



@Override

public int getArea() {

return length*length;

}

}



class Triangle extends Shape

{

protected int height;

protected int base; @Override

public int getArea() {

return this.height * this.base / 2;

}

} Shape square = new Square(4);

square.getArea(); // 4



Shape triange= new Triange(2,4);

triange.getArea(); // 4

The code above very clearly demonstrates the subtype polymorphism. We have a base type Shape and two subtypes (specifications): Square and Triangle . Method getArea() here represents the definition of polymorphism: “same name, different logic”.

polymorphism: “same name, different logic”

Now let’s move back to our “Animal” class again and implement the polymorphic way :

public abstract class Animal {

public abstract String say();

} public class Dog extends Animal{

@Override

public String say() {

return "Bark";

}

} public class Cat extends Animal{

@Override

public String say() {

return "Meow";

}

} public class Lion extends Animal{

@Override

public String say() {

return "Roar";

}

}

See now the Animal class is so simplified and all the logics are moved to the respective classes.

The above implementation is appreciating the single responsibility principle by encapsulating the behaviour/responsibility in the corresponding classes. At first, the behaviour of all the animals is encapsulated in one class making the class to violate the single responsibility principle. Being made into separate subclasses now we got the responsibilities pushed down into them so any change in the behaviour of “lion” its lion’s responsibility now not of “Animal”.

Above implementation also adheres the open/closed principle, let's see how,

private static String getSoundPolymorphicWay(Animal animal){

return animal.say();

}

Look at the above method which is depending on the abstraction (Animal class), so any class that implementing the abstraction will work on this method. Also, if it is required to implement a new behaviour for say “tiger” we don’t have to make any change to the neither in the implementation getSoundPolymorphicWay() nor in the abstraction Animal.

We can just create a new class for “Tiger”. This proves “Animal” is open for further extension still forcing no modification neither in implementations like “getSoundPolymorphicWay” nor in itself.

Benefits

This technique adheres to the Tell-Don’t-Ask principle: instead of asking an object about its state and then performing actions based on this, it’s much easier to simply tell the object what it needs to do and let it decide for itself how to do that.

Removes duplicate code. You get rid of many almost identical conditionals.

If you need to add a new execution variant, all you need to do is add a new subclass without touching the existing code (Open/Closed Principle).

Thank you for reading my article. If you think its useful, please do share, like, clap, comment ✌☺