Replacing Conditionals with Patterns

Since switches and if-statements are the most common control flow statements in many languages and are widely used throughout the code, removing the unnecessarily used ones will have a huge impact on the general readability of the code.

Use Null-Object Pattern

Always try not to return null references, instead practice the Null-Object Pattern and return Null-Objects that are neutral to operations and processes.

Suppose a module is responsible for specific calculations for a person’s properties on which taxes are paid.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class PropertyTaxCalculator {
void calculate(String personId) {
Person person = getPerson(personId);
if (person != null) {
TaxPayments taxPayments = person.getTaxPayments(2012);
if (taxPayments != null) {
PropertyTax propertyTax = taxPayments.get(PROPERTY_TAX);
if (propertyTax != null) {
List<Property> properties = propertyTax.getProperties();
if (properties != null) {
for(Property property : properties) {
if (property != null) {
// do the main calculations here
}
}
}
}
}
}
}
}

The snippet below does the same using Null-Object Pattern. Notice how applying this pattern removes the necessity for excessive null checks in the second revision of the ‘PropertyTaxCalculator’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PropertyTaxCalculator {
void calculate(String personId) {
List<Property> properties =
getPerson(personId)
.getTaxPayments(2012)
.get(PROPERTY_TAX)
.getProperties();
for(Property property : properties) {
// do the main calculations here
}
}
}

Use Strategy Pattern

Strategy Pattern is another tool in the belt to get rid of conditional statements. The following pseudo-code demonstrates an example where an operation happens on an object based on a specific condition (the client ‘type’ in our example).

1
2
3
4
5
6
7
8
9
10
11
12
class Client {
Type type;
void process(Object object) {
if (type == TypeA) {
operationA(object);
} else if (type == TypeB) {
operationB(object);
} else if (type == TypeC) {
operationC(object);
}
}
}

Since the client has a certain type at each moment, we can move the logic in each leg of the conditionals to a different strategy implementation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Client {
IStrategy strategy;
void process(Object object) {
strategy.process(object);
}
}
interface IStrategy {
void process(Object object);
}
class StrategyA implements IStrategy {
public void process(Object object) {
operationA(object);
}
}
class StrategyB implements IStrategy {
public void process(Object object) {
operationB(object);
}
}
class StrategyC implements IStrategy {
public void process(Object object) {
operationC(object);
}
}
// a newly added condition
class StrategyD implements IStrategy {
public void process(Object object) {
operationD(object);
}
}

It might look like using the strategy we ended up with twice as much code, however, the separation of concerns into different classes, encourages single responsibility and at the same time it makes them more readable and more testable. Besides, note that new conditions can be added without the need for modifying the client code.

Use Factory-Method Pattern

In case of encountering repeated conditionals that are used for object instantiation, the Factory-Method Pattern might be useful.

The pseudo-code below might look a bit long but it basically defines two behaviors each of which needs a ‘Product’ which is instantiated conditionally.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Client {
Type type;
void action1() {
IProduct product = null;
if (type == TypeA) {
product = new ProductA();
} else if (type == TypeB) {
product = new ProductB();
} else if (type == TypeC) {
product = new ProductC();
}
operation1(product);
}
void action2() {
IProduct product = null;
if (type == TypeA) {
product = new ProductA();
} else if (type == TypeB) {
product = new ProductB();
} else if (type == TypeC) {
product = new ProductC();
}
operation2(product);
}
}

Such repeating conditional object instantiation is a sign of need for refactoring into Factory-Method Pattern as suggested below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Client {
Type type;
void action1() {
operation1(ProductFactory.create(type));
}
void action2() {
operation2(ProductFactory.create(type));
}
}
class ProductFactory {
static IProduct create(Type type) {
switch (type) {
case TypeA: return new ProductA();
case TypeB: return new ProductB();
case TypeC: return new ProductC();
default: return new NullProduct();
}
}
}

Conclusion

Although it might not be necessary nor feasible to get rid of many of the conditionals (such as when comparing primitive objects or enums), most unnecessary ones can be replaced by polymorphism and patterns which will improve code readability, testability and advocates separation of concerns.

Share Comments