If you have been coding for a system for some time, you may have noticed how the code gets cluttered, hard to read and maintain. It is not anyone’s fault, really. Just that, over time, requirements change and sometimes to meet delivery deadlines, one builds something up really quick to meet the deadline. While this quick delivery will please upper management, as an engineer it will only cause problems for you in the long run. Over time, the code will just get more messier and it will be hard to read even for you if you come back to it after some time. Needless to say the challenges you will face onboarding juniors onto the system. Among other things, one of the things that tends to happen is that conditional logic (if then else) is so heavily implemented in some methods that the line count just keeps increasing. In this post, you will see how to avoid this by planning ahead and applying certain patterns during execution. There are a few ways to go about it but this post will discuss how to replace multiple if statements in code by applying the factory pattern or using enums. This post will include code samples for Java. Regarding Javascript, Python or C++, if you understand the concepts here then you can apply them to those languages quite easily.
Scenario
Say you are building an app to monitor daily spending where every transaction is categorised. Initially the requirements are that every transaction is categorised as either shopping or bill. When you start your class would look something like this
import java.math.BigDecimal;
enum TransactionType {
SHOPPING,
BILL;
}
public class DailyTransaction {
//....
public void categorise(TransactionType type, BigDecimal amount) {
if(type.equals(TransactionType.SHOPPING)) {
//code to categorise the transaction
} else if(type.equals(TransactionType.BILL)) {
//code to categorise the transaction
}
}
}
Halfway through building the app, you get a new requirement where you need to cater for one more category, Fees. Ok, no worries that is easy to add, you can modify existing code so it looks like,
enum TransactionType {
SHOPPING,
BILL,
FEES;
}
public class DailyTransaction {
//....
public void spend(TransactionType type, BigDecimal amount) {
if(type.equals(TransactionType.SHOPPING)) {
//code to categorise the transaction
} else if(type.equals(TransactionType.BILL)) {
//code to categorise the transaction
} else if(type.equals(TransactionType.FEES)) {
//code to categorise
}
}
}
You can try and modify the spend method by using a switch statement, to look like…
public void spend(TransactionType type, BigDecimal amount) {
switch (type) {
case BILL:
// code to categorise transaction
break;
case FEES:
// code to categorise transaction
break;
case SHOPPING:
// code to categorise transaction
break;
}
}
Ok cool, you used the switch case statement, but have you really made it any better? You are still using conditional logic and it is the same code. There is nothing different about it. hmm what is that phrase? Putting lipstick on a pig! Not sure if that phrase applies here but you get the idea. So how else can you write this code? Can it be written in a slightly more modern and mature way? Of course yes, by applying a pattern.
Replace multiple if statements – Factory…
Of course, you can, but take a step back and understand the problem. The end goal of your code is, categorise transactions, so for each transaction type be it bill, fees or shopping, the code will categorise the transaction. Irrespective of the logic each category may have the high-level goal is to categorise a transaction. A transaction can be in any one of the categories. Now let’s define an interface for a transaction.
public interface Transaction {
void categorise(BigDecimal amount);
}
Interfaces in Java or Typescript (or protocols in Swift) are the best. They provide a blueprint for creating classes such that we can be sure that the implementing class will have an implementation of the method defined in the interface. Now you can create a class that implements for each expense type that implements that interface.
public class Bill implements Transaction {
@Override
public void categorise(BigDecimal amount) {
//code to categorise the transaction
}
}
public class Fees implements Transaction {
@Override
public void categorise(BigDecimal amount) {
//code to categorise the transaction
}
}
public class Shopping implements Transaction {
@Override
public void categorise(BigDecimal amount) {
//code to categorise the transaction
}
}
Up to now for each TransactionType, you have a corresponding class that is capable of categorising that transaction. In order to retrieve the class that corresponds to a transaction type, you can map each transaction type to an implementation. You can create a class with a static method that returns the implementation based on the transaction_type. You can call it TransactionFactory.
public class TransactionFactory {
private static Map<TransactionType, Transaction> expenseMap = new HashMap<>() {{
put(TransactionType.BILL, new Bill());
put(TransactionType.SHOPPING, new Shopping());
put(TransactionType.FEES, new Fees());
}};
public static Transaction getProcessorFor(TransactionType type) {
return expenseMap.get(type);
}
}
Remember your spend method in the DailyTransaction class takes as input a transaction type and the amount to categorise the transaction. Then change your spend method in the DailyTransaction class to look like this,
public void spend(TransactionType type, BigDecimal amount) {
Transaction transactionProcessor = TransactionFactory.getProcessorFor(type);
transactionProcessor.categorise(amount);
}
You see the difference? The spend method is simpler, more test (able) as well easier to understand and maintain.
Can the TransactionType enum be used any other way?
Of course it can. There is an alternative to the factory approach suggested above and that is by using the enum itself that you defined. Yes of course you can, let us see how,
Replace multiple if statements – Enums
public enum TransactionType {
BILL {
@Override
public void categorise(BigDecimal amount) {
// code to categorise the transaction
}
},
FEES {
@Override
public void categorise(BigDecimal amount) {
// code to categorise the transaction
}
},
SHOPPING {
@Override
public void categorise(BigDecimal amount) {
//code to categorise the transaction
}
};
public abstract void categorise(BigDecimal amount);
}
As you can see you have declared an abstract method in your TransactionType enum and each type of transaction implements that method. Then to use the newly defined methods in the enum your spend method in the DailyTransaction class should look like this,
public class DailyTransaction {
public void spend(TransactionType type, BigDecimal amount) {
type.categorise(amount);
}
}
Again notice how simple it all looks when compared to that long method with multiple if then else statements? Applying a pattern like this is the way to go for methods that have a lot of if statements.
Replace multiple if statements – What is the command pattern?
In addition to the above methods you can also apply a command pattern. A command pattern is very similar to the first approach in which you can define an interface followed by a class that implements it. With command pattern as the name suggests, it is about executing a command. So in the context of this problem, you would define a CategoryCommand with an execute method that accepts the amount and when categorises the transaction on execution. This pattern is great but does not apply very well to the problem you are solving here.
In addition to the command pattern you can model your solution to a rule engine or an apply a similar concept to a Finite State Machine. There are several ways to go about this all of which are beyond the scope this post.
Summary
Refactoring code is not easy but it is necessary. If you are an experienced engineer you know how requirements change, code evolves over time and it keeps getting messier. If you are just starting out your career in coding, then applying such design patterns will only help you in your career. Should you refactor all areas of the code that have conditional logic? Not really, as mentioned above only areas of code where there are too many if then else statements. You know your system better than most people, so it is up to you to make that judgement call off when to refactor code.
Regardless of the level you are at, always think about the problem you are solving before writing any code. This is probably “preaching to the choir” but these things happen, you are working on a system and you get new requirements that need to go live ASAP. There’s panic and before you know it, you will quickly put together something to meet an unrealistic deadline. Take a step back and carefully think about the problem, if you apply the right pattern, solving a similar problem will only be easier and potentially require less time in the future.
When you are building a system before you write code take some time to analyse the requirements, try and anticipate the problems your code may have in the future.
Conclusion
In this post you saw code on how to replace multiple if statements by applying the factory pattern as well as using enums to refactor your Java code to make it look cleaner as well as easier to maintain. Hope this posts gives you enough confidence to be able to refactor your code. If this post does not help, then please do an online search for this and I am sure it will bring up some good results. Either way, if you have a method that is 100+ lines long because of multiple if statements, then please refactor it and make your code more modern.
If you find any of my posts useful and want to support me, you can buy me a coffee 🙂
https://www.buymeacoffee.com/bhumansoni
Have a read of some of my other posts,
What is Javascript event loop? – My Day To-Do (mydaytodo.com)
How to build a game using Vanilla Javascript – My Day To-Do (mydaytodo.com)
Vanilla Javascript: Create Radio Buttons (How-To) – Bhuman Soni (mydaytodo.com)
Java Spring Boot & Vanilla Javascript solution – My Day To-Do (mydaytodo.com)
Vanilla Javascript: Create Radio Buttons (How-To) – Bhuman Soni (mydaytodo.com)
While you are here, maybe try one of my apps for the iPhone.
0 Comments