スイッチに対する複数の方法の利点


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私はそれについて考えましたが、この質問はより具体的にはコードと最適なソリューションを見つけることに関するものであり、したがってスタックオーバーフローに適していると思います。そして、@ yes123が言うように、より多くの人々がここで応答する可能性があります。

__callは悪いです。これはパフォーマンスを完全に低下させます。これを使用して、外部の呼び出し元に対してプライベートであるはずのメソッドを呼び出すことができます。

Oop各メソッドを記述するためのphpdocを持つことができます。これは、一部のIDE(NetBeansなど)で解析できます。
binaryLV

fluffycat.com/PHP-Design-Patterns/… .. Switchはそれほど効率的ではないようです。-1

@GordonM:問題のクラスにプライベートメソッドがない場合はどうなりますか?
JAB

回答:


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

OK、ここで部分的に冗談を言うだけです。OOP側は、ディスパッチメカニズムだけでなく、関連するセマンティクスに依存するため switch文の使用に関する引数は、このような単純な例では強く作成できません。

多くの場合、Switchステートメントはクラスまたは分類の欠落を示していますが、必ずしもそうではありません。時にはswitchステートメントは単なるswitchステートメントです。


「メカニズムではなく意味論」の+1
ハビエル14年

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がなぜ悪いのかというコメントを追加します-常にTRUEを返すため、そのオブジェクトまたはそのインスタンスのis_callable()機能を台無しにします。

に更新しましis_callableたが、call_user_funcを残しました(この質問の一部ではありません)。他に渡す引数があるかもしれません。しかし、これがプログラマーに移行した今、@ Andreaの答えがはるかに優れていることに間違いなく同意します:)
ロブ

0

実行中のコードを関数に配置するだけでなく、完全なコマンドパターンを実装し、それぞれを共通のインターフェイスを実装する独自のクラスに配置します。このメソッドを使用すると、IOC / DIを使用してクラスのさまざまな「ケース」を結び付けることができ、長期間にわたってコードにケースを簡単に追加および削除できます。また、SOLIDプログラミングプリンシパルに違反しない優れたコードも提供します。


0

この例は悪いと思います。引数$ whereを受け入れるgo()関数がある場合、関数でswitchを使用するのに完全に有効です。単一のgo()関数を使用すると、すべてのgo_where()シナリオの動作を簡単に変更できます。さらに、クラスインターフェイスを保持します。さまざまなメソッドを使用する場合、新しい宛先ごとにクラスのインターフェイスを変更します。

実際には、スイッチはメソッドのセットではなくクラスのセットで置き換える必要があります-これはポリモーフィズムです。次に、宛先は各サブクラスによって処理され、すべてのサブクラスに単一のgo()メソッドがあります。条件付き置換をポリモーフィズムに置き換えることは、Martin Fowlerが説明した基本的なリファクタリングの1つです。ただし、ポリモーフィズムは必要ない場合があり、切り替えがその方法です。


インターフェイスを変更せず、サブクラスに独自の実装があるgo()場合、Liskov Substitution原則に簡単に違反する可能性があります。あなたがより多くの機能を追加している場合go()、あなたはないこれまで私がconcerendだとして、インターフェイスを変更したいです。
爆発薬
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.