12

I received a code review from a senior developer today asking "By the way, what is your objection to dispatching functions by way of a switch statement?" I have read in many places about how pumping an argument through switch to call methods is bad OOP, not as extensible, etc. However, I can't really come up with a definitive answer for him. I would like to settle this for myself once and for all.

Here are our competing code suggestions (php used as an example, but can apply more universally):

class Switch {
   public function go($arg) {
      switch ($arg) {
         case "one":
            echo "one\n";
         break;
         case "two":
            echo "two\n";
         break;
         case "three":
            echo "three\n";
         break;
         default:
            throw new Exception("Unknown call: $arg");
         break;
      }
   }
}

class Oop {
   public function go_one() {
      echo "one\n";
   }
   public function go_two() {
      echo "two\n";
   }
   public function go_three() {
      echo "three\n";
   }
   public function __call($_, $__) {
      throw new Exception("Unknown call $_ with arguments: " . print_r($__, true));
   }
}

Part of his argument was "It (switch method) has a much cleaner way of handling default cases than what you have in the generic __call() magic method."

I disagree about the cleanliness and in fact prefer call, but I would like to hear what others have to say.

Arguments I can come up with in support of Oop scheme:

  • A bit cleaner in terms of the code you have to write (less, easier to read, less keywords to consider)
  • Not all actions delegated to a single method. Not much difference in execution here, but at least the text is more compartmentalized.
  • In the same vein, another method can be added anywhere in the class instead of a specific spot.
  • Methods are namespaced, which is nice.
  • Does not apply here, but consider a case where Switch::go() operated on a member rather than a parameter. You would have to change the member first, then call the method. For Oop you can call the methods independently at any time.

Arguments I can come up with in support of Switch scheme:

  • For the sake of argument, cleaner method of dealing with a default (unknown) request
  • Seems less magical, which might make unfamiliar developers feel more comfortable

Anyone have anything to add for either side? I'd like to have a good answer for him.

10
  • @Justin Satyr I thought about that, but I think that this question is more specifically about code and finding an optimal solution and is thus appropriate for stackoverflow. And as @yes123 says, more people are likely to respond here.
    – tandu
    Jun 21, 2011 at 13:46
  • __call is bad. It completely kills performance, and you can use it to call a method that's supposed to be private to outside callers.
    – GordonM
    Jun 21, 2011 at 13:52
  • Oop allows to have phpdoc for describing each method, which can be parsed by some IDEs (e.g., NetBeans).
    – binaryLV
    Jun 21, 2011 at 13:58
  • fluffycat.com/PHP-Design-Patterns/… .. apparently Switch isn't all that efficient. -1
    – sdolgy
    Jun 21, 2011 at 14:00
  • @GordonM: What if the class in question has no private methods?
    – JAB
    Jun 21, 2011 at 14:07

5 Answers 5

10

A switch is considered not OOP because often polymorphism can do the trick.

In your case, an OOP implementation could be this:

class Oop 
{
  protected $goer;

  public function __construct($goer)
  {
    $this->goer = $goer;
  }

  public function go()
  {
    return $this->goer->go();
  }
}

class Goer
{
  public function go()
  {
    //...
  }
}

class GoerA extends Goer
{
  public function go()
  {
    //...
  }
}

class GoerB extends Goer
{
  public function go()
  {
    //...
  }
}

class GoerC extends Goer
{
  public function go()
  {
    //...
  }
}


$oop = new Oop(new GoerB());
$oop->go();
2
  • 1
    Polymorphism is the correct answer. +1 Jun 21, 2011 at 16:46
  • 2
    This is good, but the downside is an over-proliferation of code. You have to have a class for each method and that's more code to deal with .. and more memory. Is there not a happy medium? Jul 16, 2011 at 4:45
8

for this example:

class Switch
{
    public function go($arg)
    {
        echo "$arg\n";
    }
}

OK, only partially kidding here. The argument for/against the use of a switch statement cannot be strongly made with such a trivial example, because the OOP side depends on the semantics involved, not merely the dispatch mechanism.

Switch statements are often an indication of missing classes or classifications, but not necessarily. Sometimes a switch statement is just a switch statement.

1
  • +1 for "semantics, not mechanisms"
    – Javier
    Nov 28, 2014 at 19:32
1

Perhaps not an answer, but in the case of the non-switch code, it seems like this would be a better match:

class Oop {
  /**
   * User calls $oop->go('one') then this function will determine if the class has a 
   * method 'go_one' and call that. If it doesn't, then you get your error.
   * 
   * Subclasses of Oop can either overwrite the existing methods or add new ones.
   */
  public function go($arg){

    if(is_callable(array($this, 'go_'. $arg))){
      return call_user_func(array($this, 'go_'. $arg));
    }

    throw new Exception("Unknown call: $arg");
  }

  public function go_one() {
    echo "one\n";
  }
  public function go_two() {
    echo "two\n";
  }
  public function go_three() {
    echo "three\n";
  }
}

A big part of the puzzle to assess is what happens when you need to create NewSwitch or NewOop. Do your programmers have to jump through hoops by one method or the other? What happens when your rules change, etc.

2
  • I like your answer - was going to post the same, but got ninja'ed by you. I think you should change method_exists to is_callable() to avoid problems with inherited protected methods and you can change call_user_func to $this->{'go_'.$arg}() to make your code more readable. Another thing - maby add a comment why magic method __call is bad - it ruins is_callable() functionality on that object or instance of it because it will always return TRUE.
    – XzKto
    Jun 21, 2011 at 14:26
  • I updated to is_callable, but left the call_user_func since (not part of this question), there may be other arguments to pass along. But now that this has moved over to programmers, I definitely agree that @Andrea's answer is much better :)
    – Rob
    Jun 22, 2011 at 13:28
0

Instead of just putting your executing code into functions implement a full command pattern and place each of them in their own class that implements a common interface. This method allows you to use IOC/DI to wire up the different "cases" of your class and allows you to easily add and remove cases from your code over time. It also gives you a good bit of code that doesn't violate SOLID programming principals.

0

I think the example is bad. If there is a go() function that accept argument $where, if's perfectly valid to use switch in the function. Having single go() function makes it easier to modify the behaviour of all go_where() scenarious. Additionally, you will preserve the class interface - if you use various methods, you are modifying the interface of the class with each new destination.

Actually switch should not be replaced with set of methods, but with set of classes - this is polymorphism. Then the destination will be handled by each subclass, and there will be single go() method for all of them. Replace conditional with polymorphism is one of the basic refactorings, described by Martin Fowler. But you may not need polymorphism, and switch is the way to go.

1
  • If you don't change the interface and a subclass has its own implementation of go() it can easily violoate the Liskov Substitution principle. If you're adding more functionality to go(), you do want to change the interface as far as I'm concerend. Jun 21, 2011 at 15:08

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.