Switch statements are a code smell. Complex conditionals can be hard to read. The same pattern of switch
statements might be repeating in several locations.
Symptoms
There is a complex switch
statement or a long sequence of if
statements. The problem is not necessarily the switch
itself but duplication. The code can be spread in different places. Adding a new condition means finding all the switch
statements and changing them.
Solutions
Most of times when you see switch
statements you should think of polymorphism. It is typical that the conditional uses some kind of type code.
- If you need to isolate a
switch
, extract a method and possibly move the method. - If the
switch
is based on a type code, the simplest thing to do is to replace the type code with subclasses. - If you update the type code after the object is created, or already subclass this class for another reason, you have to replace the type code with state or strategy.
- Once you have the inheritance structure in place, you can replace conditional with polymorphism.
- If one of the conditional options is
null
, introduce a null object.
Replace type code with subclasses
Here we have a set of numbers that form a list of allowable values for a type code. The numbers are given understandable names via constants.
public class Shape {
public static final int SQUARE = 0;
public static final int CIRCLE = 1;
public static final int TRIANGLE = 2;
private int type;
public Shape(int type) {
this.type = type;
}
}
Create a subclass for each value of type code. There will be a static factory method that has a switch statement but at least it will be the only one.
public abstract class Shape {
public static final int SQUARE = 0;
public static final int CIRCLE = 1;
public static final int TRIANGLE = 2;
public abstract int getType();
public static create(int type) {
switch (type) {
case SQUARE:
return new Square();
case CIRCLE:
return new Circle();
case TRIANGLE:
return new Triangle();
}
}
}
public class Square extends Shape {
@Override
public int getType() {
return Shape.SQUARE;
}
}
public class Circle extends Shape {
@Override
public int getType() {
return Shape.CIRCLE;
}
}
public class Triangle extends Shape {
@Override
public int getType() {
return Shape.TRIANGLE;
}
}
Replace type code with state or strategy
Here we have a same kind of example but let’s assume that we cannot create subclasses for the coded types.
public class Shape {
public static final int SQUARE = 0;
public static final int CIRCLE = 1;
public static final int TRIANGLE = 2;
private int type;
public Shape(int type) {
this.type = type;
}
}
Create a new class and give it a name that fits the purpose of the type code. Then create subclasses for each of the types.
public class Shape {
private ShapeType type;
public Shape(int type) {
setTypeCode(type);
}
public int getTypeCode() {
return type.getTypeCode();
}
public void setTypeCode(int type) {
type = ShapeType.create(type);
}
}
public abstract class ShapeType {
static final int SQUARE = 0;
static final int CIRCLE = 1;
static final int TRIANGLE = 2;
public abstract int getTypeCode();
public static ShapeType create(int code) {
switch (code) {
case SQUARE:
return new Square();
case CIRCLE:
return new Circle();
case TRIANGLE:
return new Triangle();
}
}
}
public class Square extends ShapeType {
@Override
public int getTypeCode() {
return ShapeType.SQUARE;
}
}
public class Circle extends ShapeType {
@Override
public int getTypeCode() {
return ShapeType.CIRCLE;
}
}
public class Triangle extends ShapeType {
@Override
public int getTypeCode() {
return ShapeType.TRIANGLE;
}
}
Replace conditional with polymorphism
Let’s assume that we have already replaced a type code with subclasses and the inheritance structure is in place.
public class Shape {
// ...
public double width;
public double height;
public double radius;
public double area() {
switch (getType()) {
case ShapeType.SQUARE:
return width * height;
case ShapeType.CIRCLE:
return PI * radius * radius;
case ShapeType.TRIANGLE:
return width * height / 2.0;
}
}
}
For each of the subclasses redefine the method that has the conditional. Then delete the branch from the conditional until all conditionals have been removed.
public abstract class Shape {
// ...
public abstract double area();
}
public class Square extends Shape {
// ...
public int width;
public int height;
@Override
public double area() {
return width * height;
}
}
public class Circle extends Shape {
// ...
public double radius;
@Override
public double area() {
return PI * radius * radius;
}
}
public class Triangle extends Shape {
// ...
public int width;
public int height;
@Override
public double area() {
return width * height / 2.0;
}
}
Introduce a null object
Having a method possibly return null
means that you have to do null
checks.
public class User {
private boolean authenticated;
public boolean isAuthenticated() {
return authenticated;
}
}
// Somewhere in the code
public User getCurrentUser() {
return user;
}
// ...
User user = getCurrentUser();
if (user != null && !user.isAuthenticated())
redirectToUnauthorizedPage();
Instead of null
we can return an object that has some kind of default behavior.
public class NullUser extends User {
@Override
public boolean isAuthenticated() {
return false;
}
}
// Somewhere in the code
public User getCurrentUser() {
if (user != null)
return user;
else
return new NullUser();
}
// ...
User user = getCurrentUser();
if (!user.isAuthenticated())
redirectToUnauthorizedPage();
Benefits
Replacing switch
statements has several benefits:
- Control flow code can be bulky. Moving code to subclasses follows the Single Responsibility Principle.
- If you need to add a new type code, you can just add a new subclass without touching the existing code. This follows the Open/Closed Principle.
- Instead of asking an object for its state and performing actions based on that it is easier to let the object decide what to do. This means following the Tell, Don’t Ask Principle.
- In addition, removes duplicate code when you have several similar conditions.
Exceptions
In some cases replacing switch
statements is not necessary:
- If the
switch
operator performs very simple actions, there is no reason to change it. - A factory method or an abstract factory might use switch statements to create classes.
Summary
Switch statements are a code smell. Duplicated conditionals make changing the code hard.
Usually when you see switch
statements operating on type codes you should think of replacing them with subclasses. If this is not possible, try replacing the type code with state or strategy. Null conditionals should be replaced with null
objects.
Refactoring the switch
statements make the code follow several object-oriented programming principles.
In very simple cases or when implementing factories, there is no reason to change the switch
statements.