多种方法优于切换的优势


12

我今天收到一位高级开发人员的代码审查,询问“顺便问一下,您反对通过switch语句分派功能吗?” 我已经在很多地方读到了关于如何通过切换到调用方法来泵送参数是不好的OOP,而不是可扩展的,等等。但是,我无法真正为他提供明确的答案。我想一劳永逸地解决这个问题。

这是我们的竞争代码建议(以php为例,但可以更普遍地应用):

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));
   }
}

他的部分论点是“与普通的__call()魔术方法相比,它(切换方法)在处理默认案例方面有一种更干净的方法。”

我不同意清洁度,实际上更喜欢打电话,但我想听听别人怎么说。

我可以提出支持该Oop方案的论点:

  • 就您必须编写的代码而言,它更简洁(更少,更易于阅读,更少考虑的关键字)
  • 并非所有动作都委派给单个方法。此处的执行没有太大区别,但至少文本更加分隔。
  • 同样,可以在类中的任何位置而不是特定位置添加另一种方法。
  • 方法带有名称空间,这很好。
  • 不适用于此处,而是考虑Switch::go()对成员而不是参数进行操作的情况。您必须先更改成员,然后再调用方法。因为Oop您可以随时独立地调用方法。

我可以提出支持该Switch方案的论点:

  • 为了争辩,使用更干净的方法处理默认(未知)请求
  • 似乎不那么神奇,这可能会使不熟悉的开发人员感到更自在

任何人都可以添加任何内容吗?我想给他一个很好的答案。


@Justin Satyr我曾考虑过这一点,但我认为这个问题更具体地是关于代码和寻找最佳解决方案的,因此适合于stackoverflow。正如@ yes123所说,更多的人可能会在这里做出回应。

__call不好。它完全破坏了性能,您可以使用它来调用本应为外部调用者专用的方法。

Oop允许使用phpdoc来描述每种方法,可以由某些IDE(例如NetBeans)进行解析。
binaryLV 2011年

fluffycat.com/PHP-Design-Patterns/… ..显然Switch并不是那么高效。-1

@GordonM:如果所讨论的类没有私有方法该怎么办?
JAB

Answers:


10

交换机不是OOP,因为通常多态性可以解决问题。

在您的情况下,OOP实现可能是这样的:

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();

1
多态是正确的答案。+1
Rein Henrichs

2
这样做很好,但缺点是代码过多。您必须为每个方法都有一个类,这需要更多的代码来处理..和更多的内存。没有快乐的媒介吗?
爆炸药

8

对于此示例:

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

好的,这里只是在开玩笑。对于此类琐碎的示例,不能强烈地提出使用switch语句的观点,因为OOP端取决于所涉及的语义,而不仅取决于分派机制

switch语句通常表示缺少类或分类,但不一定。有时switch语句只是switch语句


为“语义学而不是机制” +1
哈维尔2014年

1

也许不是答案,但是对于非转换代码,看起来这将是更好的匹配:

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";
  }
}

要评估的难题中很大一部分是当您需要创建NewSwitch或创建时会发生什么NewOop。您的程序员是否必须通过一种或另一种方法来解决问题?当您的规则更改时会发生什么,等等。


我喜欢您的答案-打算发布相同的内容,但被您忍者了。我认为您应该将method_exists更改为is_callable()以避免继承的受保护方法出现问题,并且可以将call_user_func更改为$ this-> {'go _'。$ arg}()以使您的代码更具可读性。另一件事-maby添加一个注释,说明魔术方法__call为什么不好-它破坏了该对象或其实例的is_callable()功能,因为它将始终返回TRUE。

我更新到is_callable,但是由于(不是此问题的一部分)离开了call_user_func,可能还会传递其他参数。但是,既然这已经转移给程序员了,我绝对同意@Andrea的答案要好得多:)
Rob

0

不仅仅是将执行代码放入函数中,还可以实现完整的命令模式,并将每个命令模式放在实现公共接口的自己的类中。这种方法允许您使用IOC / DI来连接类的不同“案例”,并允许您随时间轻松地从代码中添加和删除案例。它还为您提供了很多不违反SOLID编程原理的代码。


0

我认为这个例子很糟糕。如果有一个接受参数$ where的go()函数,则在该函数中使用switch是完全有效的。具有单个go()函数可以更轻松地修改所有go_where()方案的行为。另外,您将保留类接口-如果使用各种方法,则将用每个新的目标修改类的接口。

实际上,switch不应替换为方法集,而应替换为类集-这是多态性。然后,目标将由每个子类处理,并且所有子类都有一个go()方法。Martin Fowler描述了用多态替换条件是基本重构之一。但是您可能不需要多态性,而切换是必须的。


如果您不更改接口,并且子类具有其自己的实现,go()则可以轻松违反Liskov替换原理。如果您要向中添加更多功能go(),则确实要更改接口,以我的意愿。
爆炸药
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.