Replacing Multiple Conditionals with Polymorphism and Composition
It’s a fairly well known refactoring pattern to replace conditionals with polymorphism. If you’re not familiar with the pattern, you can check it out here. But that basic solution can start to break down once there are multiple fields in the class that conditional checks are based off of. We’ll look into some possible ideas on how to work with these possibilities.
A Simple Case
There are a lot of ways this can go, so we’re going to work from the easiest to the hardest, always working with simple examples to have as little clutter in the way as possible. So, what is the simplest case? Take a look:
public class ClassWithConditionals { private boolean conditional1; private EnumeratedType conditional2; public ClassWithConditionals(boolean cond1, EnumeratedType cond2) { conditional1 = cond1; conditional2 = cond2; } public void method1() { if(conditional1) { //do something } else { //do something else } } public void method2() { switch(conditional2) { case CASE1: //do something break; case CASE2: //do something else break; case CASE3: //do something entirely different break; } } } enum EnumeratedType { CASE1, CASE2, CASE3 }
So, in this example, we have two different fields that ClassWithConditionals
uses in its methods. In a proper example, you’d assume more methods than just the two given, but we only need the two for the example. If you’ve only got the one method for each of the conditionals, then you don’t have much to worry about, since the maintenance cost is still low. But, as soon as the number of methods doing conditional checks like this increases, you should consider this refactoring.
The Fix
Normally, if you were to follow Replace Conditional with Polymorphism, you’d end up with six classes to fix this: one for each combination of the boolean
and enum
. Instead of that, we’ll use composition.
So, what’s the first step? First, we should probably work on the enum
erated type. enum
s can have their own methods, and those can be defined in a way that allows it to do different things based on the specific enum
. So let’s change
to look like this:enum
eratedType
enum EnumeratedType { CASE1(){ public void doSomething() { //do something } }, CASE2(){ public void doSomething() { //do something else } }, CASE3(){ public void doSomething() { //do something entirely different } }; public abstract void doSomething(); }
Now, method2
simply needs to delegate itself to conditional2.doSomething()
.
Now let’s fix the boolean
. We make an interface that is private to all but the enclosing class (and possibly the package, for the sake of tests), called Conditional1
. Then we subclass it with True
and False
. Here’s the code:
interface Conditional1 { static Conditional1 TRUE = new True(); static Conditional1 FALSE = new False(); void doSomething(); } class True implements Conditional1 { public void doSomething() { //do something } } class False implements Conditional1 { public void doSomething() { //do something else } }
I decided to make the TRUE
and FALSE
instances on the interface for a simple reason: They’re both stateless classes, which means there’s no point in having more than one instance of either. It also allows us to call for them as if they were enum
s.
Again, now the main class simply needs to delegate. Here’s what the fixed class looks like now
public class ClassWithConditionals { public static ClassWithConditionals with(boolean cond1, EnumeratedType cond2) { Conditional1 conditional1; if(cond1) conditional1 = Conditional1.TRUE; else conditional1 = Conditional1.FALSE; return new ClassWithConditionals(conditional1, cond2); } private Conditional1 conditional1; private EnumeratedType conditional2; ClassWithConditionals(Conditional1 cond1, EnumeratedType cond2) { this.conditional1 = cond1; this.conditional2 = cond2; } public void method1() { conditional1.doSomething(); } public void method2() { conditional2.doSomething(); } }
There’s something odd here. We’ve replaced one conditional with another. Our constructor is nice enough to simply accept a Conditional1
, but we have a static factory method that still takes the boolean
and does a conditional check on that.
Taking into account that we technically wouldn’t refactor this code unless there were multiple methods that were doing checks, we’ve taken many checks and put it down into one. Also, conditionals are generally regarded as being okay in Factories, forcing all checks into one place and allowing polymorphism to take over from there. You don’t have to use static factory methods as your factory, but it’s the quickest and easiest to set up on the fly. An additional benefit to allowing the code that calls the creation code of the new ClassWithConditionals
object to still be able to pass in boolean
s the way it used to, is that it allowed us to encapsulate and hide the implementation details of the conditional-based classes. Creators of the new ClassWithConditionals
don’t need to worry about creating a Conditional1
object, or even knowing that it exists.
We still wanted the constructor to take in a Conditional1
object for two reasons: 1) it keeps the conditional logic in a factory, rather than the constructor, which is preferred, and 2) it allows us to pass in test doubles of Conditional1
objects.
In fact, because of point 2, we should often consider transforming our enum
s into something more like Conditional1
, with its static instances. This will allow you to use test doubles even more. It’ll also help with inheritance or extending via composition, which I’ll discuss in a little bit.
Expanding on the Idea
There are a lot of little variations that can come to mind. First off, conditionals don’t require a boolean
or enum
. There can be a set of conditional expressions based off a number, or anything else. Often, in these cases, we replace the checks with a small helper method to make it clearer, i.e. if(numberOfPeople <= 3)...
becomes if(isACrowd(numberOfPeople))...
. We can take that a step further and create a hierarchy of GroupsOfPeople
that are created via a factory. If the factory is given a 1, it returns a SinglePerson
; given a 2, it returns a Company
object; given a 3 or more, it returns a Crowd
object. Each of these objects will have their own methods and such that can help reduce the amount of code in the original class.
Another variation is when different sets of conditional fields are layered together (if(condition1 && condition2)
, etc). To deal with this, you could go the inheritance route and create the explosion of classes to cover all the combinations. Another option is replacing one of the conditional objects with the small hierarchy that accepts the other conditional object(s) in the delegated-to methods where it would still have some conditional code, but less, more readable conditional code. For example, you could convert a class that uses two booleans to something like this:
public class ClassWithConditionals { public static ClassWithConditionals with(boolean condition1, boolean condition2) { Conditional1 cond1; if(condition1) cond1 = Conditional1.TRUE; else cond1 = Conditional1.FALSE; return new ClassWithConditionals(cond1, condition2); } private Conditional1 condition1; private boolean condition2; ClassWithConditionals(Conditional1 condition1, boolean condition2) { this.condition1 = condition1; this.condition2 = condition2; } public void method() { condition1.method(condition2); } } interface Conditional1 { static Conditional1 TRUE = new True(); static Conditional1 FALSE = new False(); void method(boolean condition2); } class True implements Conditional1 { public void method(boolean condition2) { if(condition2) { //do something } else { //do something else } } } class False implements Conditional1 { public void method(boolean condition2) { if(!condition2) { //do something really different } //and do this } }
Condition1
‘s method
accepts a boolean, then uses that to do some more conditional processing.
Additionally, if the logic of it all allows it, you could create a set of classes to replace one of the conditionals, then have their creation code accept the other conditional(s) in order to decide part of their creation. For example:
public class ClassWithConditionals { public static ClassWithConditionals from(boolean condition1, boolean condition2) { return new ClassWithConditionals(Conditional1.from(condition1, condition2)); } private Conditional1 conditionOne; ClassWithConditionals(Conditional1 conditionOne) { this.conditionOne = conditionOne; } public int method() { return conditionOne.method() * -6; } } interface Conditional1 { static Conditional1 from(boolean condition1, boolean condition2) { if(condition1) return True.with(condition2); else return False.with(condition2); } int method(); } class True implements Conditional1 { public static True with(boolean condition2) { if(condition2) return new True(5); else return new True(13); } private int secondary; public True(int secondary) { this.secondary = secondary; } public int method() { return 2 * secondary; } } class False implements Conditional1 { public static False with(boolean condition2) { if(condition2) return new False((x, y) -> x - y, 31); else return new False((x, y) -> x * y, 61); } private final BinaryOperator operation; private final int secondary; public False(BinaryOperator operation, int secondary) { this.operation = operation; this.secondary = secondary; } public int method() { return operation.apply(4, secondary); } }
For True
, the second conditional decides what the secondary number in method
‘s calculation will be. In False
, it does that as well as figure out the operator to apply to the calculation.
I’m not sure something like this ever comes up, but if it does, you now know of a way to deal with it.
The Facade Pattern
Overall, this whole set of refactorings essentially changes the code from a single class to a Facade. It takes the large collection of new classes and lets you use the whole kit and kaboodle in almost the exact same way as the single class from before, with the only real difference is calling a static factory method instead of the constructor.
This isn’t particularly important; I just wanted to point it out to you.
Inheriting
Hopefully, you won’t have to worry about inheriting or “extending via composition” this class. But you just might have to.
If the extension you’re about to write only really changes the functionality of those from the conditional objects, you could simply write a new Factory that gives the constructor a new set of the conditional objects. For example, you could add this static factory method to the last version of ClassWithConditionals
:
public static ClassWithConditionals different(int value) { return new ClassWithConditionals(new SimpleConditional1(value)); }
with SimpleConditional1
looking like this
class SimpleConditional1 implements Conditional1 { private final int value; public SimpleConditional1(int value) { this.value = value; } public int method() { return value; } }
Going beyond that, you provide whatever conditional objects the original needs, plus override whatever methods you need to override.
Outro
So, that’s what I’ve figured out for replacing multiple conditionals with a more OO option. Do you have any other ways this could be done? Do you have an example that doesn’t work that you’d like me to take a whack at? Let me know, and I’ll see what can be done.
Thanks for reading.
Reference: | Replacing Multiple Conditionals with Polymorphism and Composition from our JCG partner Jacob Zimmerman at the Programming Ideas With Jake blog. |