How to deal with multiple choices based on value (enum) using strategy or functional interfaces?

Issue

I am dealing with a case when I have an enum:

public enum CurrencyType {
    BTC, ETH
}

A pojo:

public class Challenge {
    private final String walletAddress;
    private final CurrencyType currencyType;
    //getters, constructors, setters
}

A service:

@Service
public ChallengeService {
   public Challenge verifyMessage(final Challenge challenge) {
           Challenge verifiedChallenge;
       switch (challenge.getCurrencyType()) {
           case BTC:
               verifiedChallenge = verifyBTCMessage(challenge);
               break;
           case ETH:
               verifiedChallenge = verifyETHMessage(challenge);
               break;
           default:
               throw new IllegalStateException("Unexpected value: " + challenge.getCurrencyType());
       }
    return verifiedChallenge;
    }

    private Challenge verifyBTCMessage(Challenge challenge) { //implementation here }

    private Challenge verifyETHMessage(Challenge challenge) { //implementation here }

}

Currently the enum contains only 2 values, ETH and BTC, but some day the number of values in this enum can grow to 5, 10 or even more.

At this point I am thinking that my approach is wrong. I was looking at Strategy pattern, it looks nice, but it seems that at some point I won’t be able to escape the whole ifology (or switchology to be exact here) to delegate the Challenge object to adequate methods based on the CurrencyType enum value.

While reading about strategy pattern on refactoring guru, I have found this information (in the section of cons of the strategy pattern)

A lot of modern programming languages have functional type support that lets you implement different versions of an algorithm inside a set of anonymous functions.

This seems that this could be a better approach, but… I am in a beginner phase of learning functional programming, and to be completely honest – I don’t know where to start. Or maybe the strategy pattern would be sufficient but I am missing some crucial thing about it? All I want to achieve is to get rid of the switchology if possible.

Solution

I would suggest you create an interface as follows:

public interface MessageVerifier {
     public CurrencyType handles();
     public Challenge verifyMessage(Challenge challenge);
}

Then for BTC you would have something like (you will have one implementation for each CurrencyType):

@Component
public class BtcMessageVerifier implements MessageVerifier {
     public CurrencyType handles() {
         return CurrencyType.BTC;
     }

     public Challenge verifyMessage(Challenge challenge) {
         // Your logic here
     }
}

You can now inject the list of all MessageVerifiers in ChallengeService and rearrange it into a Map so that it is easier to use when needed.

@Service
public ChallengeService {
   
   private Map<CurrencyType, MessageVerifier> messageVerifierPerCurrency;

   @Autowired
   public ChallengeService(List<MessageVerifier> messageVerifiers) {
        this.messageVerifierPerCurrency = messageVerifiers.stream().collect(Collectors.toMap(MessageVerifier::handles, Function.identity()));
   }  

   public Challenge verifyMessage(final Challenge challenge) {
       Challenge verifiedChallenge;
       if (messageVerifierPerCurrency.containsKey(challenge.getCurrencyType())) {
          verifiedChallenge = messageVerifierPerCurrency.get(challenge.getCurrencyType()).verifyMessage(challenge);
       } else
          throw new IllegalStateException("Unexpected value: " + challenge.getCurrencyType());
       }
       return verifiedChallenge;
    }

}

Answered By – João Dias

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published