Why 3a? Why not Part 4? Well, a lot of people have messaged me that I am writing code in JavaScript, which due to its dynamic nature can do such things conveniently, but how about static languages like C++, Java or C#? So this post is basically same as Part 3 but using static languages.
So let's get rolling
switch-case in any language leads to problems, especially modification of existing code, regression testing, not to mention that the code starts to bloat up when more cases are added
Let's look at the beast
class Evil { | |
public String changeCase(String caseType, String… words) { | |
// We support lower and camel cases | |
// Sanitize the user input | |
caseType = caseType | |
.trim() | |
.toLowerCase(); | |
// Accumulate output in StringBuilder | |
StringBuilder result = new StringBuilder(); | |
if (caseType.equals("lower")) { | |
// All words are converted to lower | |
// And then concatenated | |
for (String word : words) { | |
result.append( | |
word | |
.trim() | |
.toLowerCase() | |
); | |
} | |
} else if (caseType.equals("camel")) { | |
// First word is all lower | |
result.append( | |
words[0] | |
.trim() | |
.toLowerCase() | |
); | |
// Subsequent words are | |
// first -> upper | |
// rest -> lower | |
for (int offset = 1; | |
offset < words.length; | |
offset++) { | |
// First letter is upper | |
result.append( | |
words[offset] | |
.substring(0, 1) | |
.toUpperCase() | |
); | |
// Remaining letters are lower | |
result.append( | |
words[offset] | |
.substring(1) | |
.toLowerCase() | |
); | |
} | |
} | |
return result.toString(); | |
} | |
public static void main(String[] args) { | |
Evil obj = new Evil(); | |
String output = obj.changeCase("lower", "This", "Word"); | |
System.out.println(output); | |
} | |
} |
Implementing function maps in Java and C# are nearly as easy as JavaScript. The simplest way is to use delegates in C#. Other ways include Action<>, Func<> and Predicate<>. Java too has Function<>, Consumer<> and Predicate<>. Everything else failing, we can still fall back to Interfaces (or abstract classes in languages like C++).
We will use the simplest of ways here.. interface
/** | |
* interface specifies just one method | |
* which converts the case and returns back | |
* | |
* Various converters will implement this | |
*. lower, camel, proper, snake etc | |
*/ | |
interface CaseConverter { | |
String convert(String… words); | |
} |
Now let’s refactor our application. We will cleave out code from individual if-else blocks and make them separate interface implementations. These implementation can be written in separate files, independent of other converters and make the unit testing much simpler
lower CaseConverter
/** | |
* LowerConverter implements CaseConverter | |
* and converts all words to lower case | |
*/ | |
class LowerConverter implements CaseConverter { | |
@Override | |
public String convert(String… words) { | |
// Accumulate result in StringBuilder | |
StringBuilder result = new StringBuilder(); | |
// All words are converter to lower | |
// And then concatenated | |
for (String word : words) { | |
result.append( | |
word | |
.trim() | |
.toLowerCase()); | |
} | |
return result.toString(); | |
} | |
} |
Now we are focussing on ONE case at a time, which leads to better unit testing and no modification is required in a HUGE switch-case of if-else ladder
camel CaseConverter
/** | |
* CamelConverter makes | |
* first word all lower | |
* subsequent words firt letter upper, rest lower | |
* "TWO", "WORDS" -> "twoWords" | |
*/ | |
class CamelConverter implements CaseConverter { | |
@Override | |
public String convert(String… words) { | |
// Accumulate result in StringBuilder | |
StringBuilder result = new StringBuilder(); | |
// First word is all lower | |
result.append( | |
words[0] | |
.trim() | |
.toLowerCase() | |
); | |
// Subsequent words are | |
// first -> upper | |
// rest -> lower | |
for (int offset = 1; | |
offset < words.length; | |
offset++) { | |
// First letter is upper | |
result.append( | |
words[offset] | |
.substring(0, 1) | |
.toUpperCase() | |
); | |
// Remaining letters are lower | |
result.append( | |
words[offset] | |
.substring(1) | |
.toLowerCase()); | |
} | |
return result.toString(); | |
} | |
} |
We have separated out each case (if block) into separate implementations, so nows it’s time to factor our main CaseConverter code. This is the crux of the whole exercise –
Replace switch-case/if-else ladder with a MAP
Convert EVIL into Angel
/** | |
* Our EVIL code has turned into an angel | |
* We now have a small and maintanable code | |
*/ | |
class Angel { | |
// Get rid of old switch-case/if-else ladder | |
// Replace it with a sweet map | |
HashMap<String, CaseConverter> converters = | |
(HashMap<String, CaseConverter>) | |
Map.of( | |
"lower", new LowerConverter(), | |
"camel", new CamelConverter() | |
); | |
public String changeCase(String caseType, String… words) { | |
// We support lower and camel cases | |
// Sanitize the user input | |
caseType = caseType | |
.trim() | |
.toLowerCase(); | |
// Lookup which converter the user wants | |
CaseConverter converter = converters.get(caseType); | |
if (converter!=null) | |
return converter.convert(words); | |
else | |
return null; | |
} | |
public static void main(String[] args) { | |
Angel obj = new Angel(); | |
String output = obj.changeCase("camel", "This", "Word"); | |
System.out.println(output); | |
} | |
} |
So there.. we have eliminated switch-case/if-else ladder from a static language like Java. We can do similar things in C#, C++ and others too.
I have not focussed on optimisation as the idea was to keep the code simple to understand the conversion process, but we can use Function<> or even SAM (Single Abstract Method) implementation, instead of creating ENTIRE class to implement individual converters.
You feedback, correction and improvement suggestions are welcome
Hi Sanjay Vyas,
thank you for efforts. I am wondering which is better the way you presented it or using polymorphisme?
thank you.
LikeLiked by 1 person
I prefer polymorphism when there is more than one method in the base implementation of which some may be overridden.
If there is just one method and that has to be overridden, then func or delegate is easier
LikeLiked by 1 person