Core Java

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 enumerated type. enums 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 enumeratedType to look like this:

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 enums.

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 booleans 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 enums 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.

Jacob Zimmerman

Jacob is a certified Java programmer (level 1) and Python enthusiast. He loves to solve large problems with programming and considers himself pretty good at design.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button