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.
CodePudding user response:
I suppose this could be implemented as follows:
interface Verifier {
boolean isMatched(CurrencyType type);
CurrencyType currency();
Challenge verify(Challenge challenge);
}
And each concrete class will be used/matched for a particular currency.
CodePudding user response:
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 MessageVerifier
s 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;
}
}