DEV Community

Cover image for Don’t use if-else blocks anymore! Use Strategy and Factory Pattern Together
Tamer Ardal
Tamer Ardal

Posted on • Edited on

Don’t use if-else blocks anymore! Use Strategy and Factory Pattern Together

As we move forward in a project, lost in if-else blocks, struggling with complex conditions and repetitive code, we look for a solution. But why should we be stuck in if-else blocks? In this article, let’s discover the way to get rid of if-else confusion together with Strategy and Factory patterns.

Problem: If-Else Confusion

Let’s say you are developing an e-commerce application and you need to support different payment methods like credit card, debit card and cryptocurrency. You start with if-else blocks to process payments:

public class PaymentService {

    public void processPayment(String paymentType) {
        if (paymentType.equals("CREDIT_CARD")) {
            System.out.println("Processing credit card payment...");
        } else if (paymentType.equals("DEBIT_CARD")) {
            System.out.println("Processing debit card payment...");
        } else if (paymentType.equals("CRYPTO")) {
            System.out.println("Processing crypto payment...");
        } else {
            throw new IllegalArgumentException("Invalid payment type");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

While it may seem simple at first, as payment methods increase, so will the if-else complexity. A new payment method means adding a new condition. The result is a pile of code that is difficult to manage. And this method is contrary to the Open-Closed Principle.

But, we can use the both Strategy and Factory patterns to solve this problem.

First, let’s create an enum:

public enum PaymentType {
    CREDIT_CARD,
    DEBIT_CARD,
    CRYPTO
}
Enter fullscreen mode Exit fullscreen mode

Solution: Cleaning with Strategy Pattern

public interface PaymentStrategy {
    void pay(PaymentRequest request);
}

public class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(PaymentRequest request) {
        System.out.println("Processing $type payment".replace("$type", String.valueOf(request.getPaymentType())));
    }
}

public class DebitCardPayment implements PaymentStrategy {
    @Override
    public void pay(PaymentRequest request) {
        System.out.println("Processing $type payment".replace("$type", String.valueOf(request.getPaymentType())));
    }
}

public class CryptoPayment implements PaymentStrategy {
    @Override
    public void pay(PaymentRequest request) {
        System.out.println("Processing $type payment".replace("$type", String.valueOf(request.getPaymentType())));
    }
}
Enter fullscreen mode Exit fullscreen mode

At this stage, a separate strategy for each payment method is implemented from a common interface. Now, with Factory Pattern, we will decide which strategy to choose.

Step 2: Choosing Strategy with Factory Pattern

In this step, we can make the Factory Pattern cleaner and optimized with EnumMap.

public class PaymentFactory {
    private static final Map<PaymentType, PaymentStrategy> strategies = new EnumMap<>(PaymentType.class);

    static {
        strategies.put(PaymentType.CREDIT_CARD, new CreditCardPayment());
        strategies.put(PaymentType.DEBIT_CARD, new DebitCardPayment());
        strategies.put(PaymentType.CRYPTO, new CryptoPayment());
    }

    public static PaymentStrategy getPaymentStrategy(PaymentType paymentType) {
        PaymentStrategy strategy = strategies.get(paymentType);

        if (Objects.isNull(strategy))
            throw new IllegalArgumentException("Strategy not found");

        return strategy;
    }
}
Enter fullscreen mode Exit fullscreen mode

Last Step: Service Class Reorganization

Now, let’s use what we have done.

public class PaymentService {

    public void processPayment(PaymentRequest request) {
        // Don't forget to check objects if null!
        if (Objects.isNull(request) || Objects.isNull(request.getPaymentType())
            throw new IllegalArgumentException("Request can not be null!");
        PaymentStrategy strategy = PaymentFactory.getPaymentStrategy(request.getPaymentType());

        strategy.pay(request);
    }
}
Enter fullscreen mode Exit fullscreen mode

As it is, we don’t need any if-else blocks for payment processing. Thanks to Strategy and Factory Patterns, our code is cleaner, modular and extensible.

Why Should We Use This Patterns?

1. Extensibility: Adding a new payment method only requires a new class and a few lines of code.
2. Readability: By using strategies and factory instead of if-else blocks, you make your code more understandable and manageable.
3. Maintainability: With the strategy and factory pattern, changes to the code can be made without affecting other pieces of code.

Conclusion: From Confusion to Clarity

If you are working on a growing project, you shouldn’t use if-else blocks. Strategy and Factory patterns are perfect solutions to make your code cleaner, modular and maintainable.

As you can see in this article, using design patterns instead of if-else blocks to manage payment transactions makes the project more developable and improves the readability of the code. Try these patterns in your next project instead of using if-else blocks.

...

Thank you for reading my article! If you have any questions, feedback or thoughts you would like to share, I would love to hear them in the comments.

You can follow me on dev.to for more information on this topic and my other posts.

Thank you!👨‍💻🚀

To follow me on LinkedIn: https://www.linkedin.com/in/tamerardal/
Medium: Don’t use if-else blocks anymore! Use Strategy and Factory Pattern Together

Top comments (44)

Collapse
 
parables profile image
Parables

Great article but the title is a bit of an over-statement....
It sounds like if-else statements are a terrible thing...

This is not a silver bullet and for newbies reading this, don't go refactoring all your if-else blocks with this...

Understanding and mastering Design Patterns is a skill every developer needs to learn but most importantly, knowing when to use the right tool for the right job.

Sometimes, a simple if-else statement is the best solution... Know when to refactor

Collapse
 
nandorholozsnyak profile image
Nándor Holozsnyák

As this is Java, we could be using Java 21's pattern matching with sealed classes.
Current setup does not yield an error if a new type is given at build time, but with sealed classes it would.

I do like this Strategy pattern, but most times with an isActive or whatever how we call method that decides if we have to launch an implementation or not, that is great of multiple impls must be executed in chain.

Collapse
 
lexlohr profile image
Alex Lohr

I know this pattern under the name of branchless programming - the factory pattern is usually an OOP pattern where you define objects through methods, ie. new MyProduct().color(Colors.Blue).size(Size.XL).build().

Collapse
 
schmoris profile image
Boris

That sounds like a version of the builder pattern to me, might be mistaken tho.

Collapse
 
mahmoudalaskalany profile image
Mahmoud Alaskalany

That is a builder pattern

Collapse
 
cicirello profile image
Vincent A. Cicirello • Edited

Your PaymentFactory class isn't a factory. In the factory pattern, a factory method instantiates new object instances. In your example, the getPaymentStrategy method returns one of three pre-existing objects depending on payment type. Call that method again with same payment type and you get same payment strategy instance. This isn't a factory.

Collapse
 
blafasel3 profile image
Maximilian Lenhardt

I like this pattern, much more concise.

I would like to suggest a little improvement: I would use a switch Statement on the payment type instead of the map inside the PaymentFactory. Then you just get a compile error because a case is missing here if you happen to add another payment type. The map will fail at runtime :)

Collapse
 
panditapan profile image
Pandita

Reads the comments

Please don't take them to heart! I've done this for when code becomes really unmanageable and it's great stuff! Sometimes what we're missing is implementing patterns in practice to understand their usefulness.

Thanks for the article!

Collapse
 
tamerardal profile image
Tamer Ardal

Thank you so much for your comment

Collapse
 
efpage profile image
Eckehard

Your titel is too much click bait for me. There is nothing wrong with IfThenElse. Any programming construct will probably be confusing if used in the wrong situation. But this is more of a problem with the programmer than with the method.

Look at this example.

if (x>0) 
   console.log("positive")
else 
   console.log("negative")
Enter fullscreen mode Exit fullscreen mode

Please show me how this could be made simpler with the factory pattern?

You can simplify this with ternary operator like this:

(x>0) ? console.log("positive") : console.log("negative")
Enter fullscreen mode Exit fullscreen mode

But this will be harder to read, as the ternary operator is meant to be used in a different way like this and in this case it will be better readable:

console.log((x>0) ? "positive" : "negative")
Enter fullscreen mode Exit fullscreen mode

There have been multiple posts about escaping the if/else hell like here, but it depends much on your task which one fit´s best.

If you need a "default"-case, you can use this:

function checkStatus(status){
   const statusList = {
      available: 'The user is currently available',
      busy: 'The user is currently busy',
      away: 'The user is currently away from keyboard',
      breaktime: 'The user is currently having a good lunc'
   }

   return statusList[status] ?? 'Option not found' 
}
Enter fullscreen mode Exit fullscreen mode

Another pattern I like much is this a special form to use the ternary operator:

let txt = 
paymentType.equals("CREDIT_CARD")  ? "Processing credit card payment..." :
paymentType.equals("DEBIT_CARD")) ? "Processing debit card payment...") :
paymentType.equals("CRYPTO")) ? "Processing crypto payment...") : false;

if (txt) System.out.println(txt) 
else throw new IllegalArgumentException("Invalid payment type");
Enter fullscreen mode Exit fullscreen mode
Collapse
 
tamerardal profile image
Tamer Ardal • Edited

Thank you very much for your comment. I think you missed the point a little bit. I did this to keep it a bit simple, but either your or my examples do not represent the real-world situation.

For example, no one logs if the payment type is a credit card. In the real world, we do some service calling, then insert or update data in the database, and finally respond to an API call.

In this scenario, every if-else block has too many code lines. This is very messy and contrary to SOLID principles. And I've seen people doing extremely complex jobs using too much if-else, which makes it very difficult for newcomers to understand the code.

Of course, if you are a beginner or actions are simple, you can use if-else blocks. I also use if-else blocks to do simple checks. Not using them for simple operations would be overengineering.

And I admit the title is a bit click-bait...

In summary, this article was not for beginners, but for those who are already working on a large project and using if-else or switch-case in complex operations.

By doing so, if another method is added in the future, we comply with the Open-Closed Principle by creating new classes without touching the existing code, rather than adding a new else if in the service.

I hope I have explained who this article is more suitable for.

Collapse
 
efpage profile image
Info Comment hidden by post author - thread only accessible via permalink
Eckehard

Dont use cars anymore, I invented a rocket...

I know things can get very messy and it is good to know solutions for that. But this is a very special solution to a very special problem and I suppose, there are many ways to solve this in a structured manner. If you tried to use If/else for that, it shows just your limited experience, it has nothing to do with limitations of the if/else construction.

Maybe you sould be a bit more careful with your titles...

Collapse
 
rgunczer profile image
Robert Gunczer

I would never replace something so simple as if-else with more abstraction and patterns.
The only thing I would do is to use "switch expressions" and that's it.

this is "merchant of complexity" category

Collapse
 
jwp profile image
John Peters

I recall reading something similar to this on CodeProject 15 years ago. Was interested then but never implemented it. Today I find it rare to have huge if then or switch statements. Thanks for reminding me....

Collapse
 
kurealnum profile image
Oscar

Just a heads up that you can add highlighting to the code blocks if you'd like. Just change:

code block with no colors example

... to specify the language:

code block with colors example

More details in our editor guide!

Collapse
 
tamerardal profile image
Tamer Ardal

Wow thanks, I didn't know that

Collapse
 
gregorygaines profile image
Gregory Gaines

I wouldn't say never use if/else. What if inside credit card payments you have chip, tap, swipe, Google Pay, Apple Pay.

Now we have ChipCardPayment, TapCardPayment, SwipeCardPayment, GooglePayCardPayment, ApplePayCardPayment.

If you don't use if/else your're stuck creating a class for every condition. What about non oop languages, they won't be able to fully follow this principle.

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more