2

I know there are already some questions about replacing if else with polymorphism, for example:

Applying Replace Conditional with Composition in functional programming

Is it wrong to use any type of parameter to determine behavior?

Should conditional logic be always coded via type system where possible?

which the following is bad:

public static void main(String[] args) {
  test("Cat");
}
public static void test(String str){
  if(str.equals("Cat")){
    System.out.println("cat");
  }else if(str.equals("Dog")){
    System.out.println("dog");
  }
}

And should wrap the "what to do" into a class and let "polymorphism" decides what should the class do:

public interface Animal{
    public void print();
}
public class Cat implements Animal{
  public void print(){
    System.out.println("cat");
  }
}
public class Dog implements Animal{
  public void print(){
    System.out.println("dog");
  }
}

public static void main(String[] args) {
  Animal animal=new Cat();
  animal.print();
}

However, I found most of the questions above is not practical for me: either the "what to do" is hardcoded (eg:Animal animal=new Cat()) or the solutions don't mention where the "what to do" from. What if my "what to do" is from dynamic data? eg:

public static void main(String[] args) {
  try{
    Animal animal = (Animal)(Class.forName("mypackage."+args[0]).getConstructor().newInstance());
    animal.print();
  }catch(Exception e){
    e.printStackTrace();
  }
}

is it still worth to replace it with polymorphism? Also I found sometimes the input name is not exactly the same as my desired class name, for example, the incoming data may use an int to represent the type (which the data source is from other API instead of my projects that under my control), so I need to modify the code like this:

public class Animal_0 implements Animal{
  public void print(){
    System.out.println("cat");
  }
}

public class Animal_1 implements Animal{
  public void print(){
    System.out.println("dog");
  }
}

public static void main(String[] args) {
  try{
    ConfigObj config=(some system function to get config); //0 = cat, 1 = dog
    Animal animal = (Animal)(Class.forName("mypackage.Animal_"+config.animalType).getConstructor().newInstance());
    animal.print();
  }catch(Exception e){
    e.printStackTrace();
  }
}

Is it still worth to use polymorphism in this case?

2
  • 3
    It's hard to come up with an answer when the question already contains so much wrongness. The examples you give are contrived and outright nonsensical (who would name classes Animal_0 and Animal_1 just because some external entity likes to encode cats and dogs as 0 and 1?). And of course, as the answers point out, you don't need classes to just replace a single if/else within your code. Commented Feb 2 at 9:14
  • It's also an intentionally contrived example. You cannot reasonably expect to deserialize data into very particular premade types unless you have already designed a way in which the specific type can be inferred; which would only occur in cases where you've already excluded using a single type as a valid deserialization target. The given example generally only arises in scenarios where the question you're asking is already answered and the design was based on the given answer. The challenge here is figuring out what it is you're imagining, rather than answering a technical question.
    – Flater
    Commented Feb 5 at 1:00

3 Answers 3

5

Your first example

public static void main(String[] args) {
   test("Cat");
}
public static void test(String str){
   if(str.equals("Cat")){
       System.out.println("cat");
   }
   else if(str.equals("Dog")){
       System.out.println("dog");
   }
}

is IMHO too short and too artificial to draw sensible conclusions from it. Still my first thought here was, why not implement it this way?

  public static void test(String str){
       System.out.println(str.toLowerCase());
  }

What I try to demonstrate here: when you have a chance to implement some code without any condition at all, by a generalized rule which makes the code simpler (!), then it is usually a good idea to implement this using neither conditionals nor polymorphism.

Moreover, you wrote that your original example is "bad", implying that "replacing conditional by polymorphism" would be "good". Such line of thinking is always a warning sign to me, since it shows a missing understanding when it is beneficial to apply "replacing conditional by polymorphism", and when not. Refactorings are never good or bad per se, it depends on the context whether they are helpful, or if not the opposite refactoring ("replacing polymorphism by conditional") is more beneficial, since it simplifies the code.

One core idea behind "replacing conditional by polymorphism" is to avoid the repetition of the specific conditional test in multiple places of the code base. It makes most sense when the decision between the different cases will be centralized in a single place in the code base. As you surely know, this is called the DRY principle. There are more implications from this refactoring, but for the sake of simplicity, let us focus on this point. As long as a specific conditional is only required once in the whole code base, it is usually not worth to refactor - quite the opposite, introducing a full class hierarchy only for saving a single "if/else" is clearly overengineered. Only when the same type of conditional occurs in multiple place of the code base (ideally 3 times or more), then you should consider to apply it (but even then, you may consider to think about alternatives first).

After applying this refactoring, there is usually a single place where conditional tests will remain - and that is the place where the objects are constructed, the "factory method". As I wrote in the beginning, sometimes you are lucky and can find a generalized rule to avoid conditional tests for each type even inside the factory - which is what you showed us in the second half of your post, by utilizing reflection. Sometimes you are not so lucky and have to make further conditional tests to make the construction work correctly. That may bother you a little bit, but can be unavoidable. Still the main goal is reached - conditional tests stay in one place, not many.

2

You should replace the conditional with a polymorphic methods when:

  1. The method works on the internal data of the classes
  2. You have more than one method on the same condition. ie your condition is "what is the sub class?"
  3. You hold the objects in memory rather than just using once.

Unless you hit all three criteria you probably aren't hitting the threshold for polymorphism yet. (Of course you still might want to plan ahead)

For example, a single program that loops through Messages and sends either an email or a notification based on a type field. Probably isn't going to benefit from adding an Email and Notification sub class.

Either way you have to run the same conditional once. To populate the class or branch the code and you only run the method/function once before discarding the object. The class is just working the same as the type field in this case.

However, if email and notification had different fields, if you are creating them in a form and then sending them multiple times in a windows client app, which holds them in memory and displays the data? Well then you have multiple cases where you want to run the same "is this a message or notification?" conditional, you have multiple functions which only relate to one of the types and you will run them multiple times during the lifespan of the object.

Here, you will save yourself code and simplify the application by creating the subclasses.

*Disclaimer, this is a rough guide.

1

There’s never just “good” and “bad”. There are advantages and disadvantages.

“Saving a conditional” is NOT a reason to use polymorphism. You will end up with a dozen classes, a dozen different constructor calls. Your knowledge about animal names (“Cat” for identifying cats, “cat” to print) times 10 for ten animals is now distributed over 10 classes, that you have to all remember.

And now you want the size of a dog. Small, medium, large, giant. So are you creating four subclasses now?

There are better ways than polymorphism. One is to store information in member variables. One is to store closures. Another is to store delegate objects. (For example, iOS has ONE class for table cells and uses delegates to implement different contents. It has ONE class for data displayed in a cell plus delegates. Every single table that you see in any iOS application is an instance of the same class).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.