0

How to follow Open Close Principle without violating LSP while deciding which method to be invoked with different parameters in a statically typed language?

Consider the requirement like

  • Action 1: perform DB operation on Table 1

  • Action 2: Perform DB operation on Table 2 based on input

  • Action 3: Do Nothing

Code for above requirement would look like

process(obj) {
    
    if(obj.type === action1) {
       db.updateTable1()
    }
    if(obj.type === action2) {
       db.updateTable2(obj.status)
    }
    if(obj.type === action3) {
       //May be log action 3 recieved
    }
}

Figured out a way to follow OCP in above code for additional actions, by moving body of if statement to method and maintain a map of keys with action as name. Reference

However feels solution is violating the OCP as method wrapping the contents of first if block will not receive any parameter, second method wrapping the contents of second if block will have a parameter.

Either it forces all method to follow the same signature in trade off following OCP but violating LSP or give up OCP itself and thereby live with multi if statements.

Mani
  • 2,599
  • 4
  • 30
  • 49

2 Answers2

1

A simple solution would be to define a strategy, which execute the code currently contained in the if / else if / else branches:

interface Strategy {
  String getType();
  void apply();
}

The strategies need to be registered:

class Executor {
  private Map<String, Strategy> strategies;

  void registerStrategy(strategy Strategy) {
    strategies.put(strategy.getType(), strategy);
  }

  void process(obj) {
    if (strategies.containsKey(obj.type)) {
      // apply might execute db.updateTable1(),
      // depending on the interface's implementation
      strategies.get(obj.type).apply();
    } else {
      System.out.println("No strategy registered for type: " + obj.type);
    }
  }
}

The tradeoffs you recognise are unfortunately what you'll have to deal with when working with OOP in Java, C++, C# etc as the systems are dynamically put together and SOLID is kind of addresses the flaws. But the SOLID principles are intended to provide guidance, I wouldn't follow them idiomatically.

I hoped to find an example by better programmers than myself illustrating the command pattern. But I was just finding really bad examples which were not really addressing your question.

The problem of defining an associating an intent (defined as string or enum, a button click) with an action (an object, a lambda function) will always require a level of indirection we have to deal with. Some layers of abstractions are acceptable, for example: never call a model or service directly in a view. You could also think of implementing am event dispatcher and corresponding listeners, which would help with the loose coupling. But at some lower level you'll have to look up all listeners ...

Florian Salihovic
  • 3,921
  • 2
  • 19
  • 26
  • Problem with above is block inside 'if' getting abused & it violates the LSP. Very sooner, the if become nightmare because of too many devs adding a condition – Mani Jan 30 '22 at 07:21
  • There are just three possible outcomes: the key and a corresponding value exists, it does not exists, or the `apply` function throws and exception. You can use `final` keyword so nobody inherits from `Executor`. I'll try my best to illustrate another example in my post. – Florian Salihovic Jan 30 '22 at 21:56
  • Actually, the other example I thought of can't work. I edit my post. – Florian Salihovic Jan 30 '22 at 22:19
0

The nature of obj is ambiguous, but I would recommend having a well-defined interface and pass it throughout your code where the class implementation of your interface would be equivalent to your 'action'. Here's an example of what that might look like in Typescript:

interface someDBInterface {
  performAction() : void;
}

function process(obj : someDBInterface) {
  let result = obj.performAction();
}

class action1 implements someDBInterface {
  status: any
  performAction() {
    //db.updateTable1();
  }
}

class action2 implements someDBInterface {
  status : any
  performAction() {
    //db.updateTable1(this.status);
  }
}

class action3 implements someDBInterface {
  performAction() {
    //May be log action 3 recieved
  }
}

If this doesn't meet your requirements, feel free to reach out :)

Jared B
  • 46
  • 6