Search code examples
javaoopsolid-principlesliskov-substitution-principle

How to efficiently share functions between classes without violating the Liskov Substitution Principle


I have a codebase that was originally created with a number of different options that allow you to get the code to perform the same process in a slightly different way, like this:

public class MainFunction {
  public void main(String option){
     if (option.equals("vanilla mode")){
        this.firstFunction();
     }else{
        this.differentVersionOfFirstFunction();
     }
     this.secondFunction();
  }

  public void firstFunction(){
    //First functions code
  }

  public void secondFunction(){
     //Second functions code
  }

  public void differentVersionOfFirstFunction(){
     // different code from First functions code that produces the same type of end result using a slightly different method
  }
}

This has gotten gradually more and more complex as various different options are added and the code becomes increasingly convoluted.

To resolve this I had originally planned to create a Parent object, that could then have children with subtlety different variations on their Parents methods when needed. The problem is that I understand that this would violate the Liskov Substitution Principle and indeed I may have children that should be sharing the same method that may not be present in their parent.

So I am left with having the different methods in the same class object, doing the same job in slightly different ways.

public class MainFunction {
  public void main1(){
    this.firstFunction();
    this.secondFunction();
  }

  public void main2(){
    this.differentVersionOfFirstFunction();
    this.secondFunction();
  }

  public void firstFunction(){
    //First functions code
  }

  public void secondFunction(){
    //Second functions code
  }

  public void differentVersionOfFirstFunction(){
    // different code from First functions code that produces the same type of end result using a slightly different method
  }
}

I suppose I could create a separate Utility class to hold all my various functions, but I was not sure if there was a more elegant solution?


Solution

  • I don't see how your examples violate the Liskov Substitution Principle. However, what I see is that they probably violate Open/Closed Principle, meaning that every time you need to modify your program, you edit some MainFunction.java file and have a chance of affecting all possible scenarios of running the program. A better solution involves having a lot of decoupled components so that when you need to modify something, you modify only a tiny portion which is unlikely to affect all scenarios the program might encounter. This is what Single Responsibility Principle is about.

    As mentioned in another answer, your scenario seems to be good fit to apply the strategy pattern. This might look as following:

    1. Create interface MainFunction with void main() method, without any options.
    2. Create an abstract strategy class such as AbstractMainFunction with a public abstract void main() member, without any options. This class will implement MainFunction interface.
    3. Create separate implementations of the AbstractMainFunction as needed, e.g. VanillaModeMainFunction and DifferentMainFunction. You may find it useful to keep some shared code in the AbstractMainFunction.
    4. Create a strategy-switcher class, e.g. MainFunctionService. It will have a method, public void main(String option) as in your first example, and probably will have a switch statement like that:

      MainFunction strategy = defaultFunction;
      switch(option) {
          case "vanilla":
               strategy = vanillaFunction;
               break;
          case "different":
               strategy = differentFunction;
               break;
      }
      strategy.main();
      

    It looks like a lot of things to do but in the end you will see how this really simplifies maintenance and further development.