web-dev-qa-db-ja.com

スイッチステートメントは悪いですか?

私は最近、特にRobert Martinの "Clean Code"(p37-39)から、switch文がOOPで悪いことを学びました。

しかし、このシーンを考えてみましょう。ゲームサーバーを作成し、クライアントからメッセージを受信します。これには、移動、攻撃、アイテムの選択などのプレーヤーのアクションを示す整数が含まれています。30以上の異なるアクションがあります。私がこれらのメッセージを処理するコードを書いているとき、私が考えるどのような解決策も、どこかでスイッチを使用する必要があります。 switchステートメントでない場合、どのパターンを使用すればよいですか?

36
Hongbo

スイッチは他の制御構造と同様です。それが最善/最もクリーンなソリューションである場所と、それが完全に不適切である場所が他にもたくさんあります。他の制御構造よりも悪用されているだけです。

OO設計では、一般的なメッセージクラスから継承するさまざまなメッセージタイプ/クラスを使用し、オーバーロードされたメソッドを使用してさまざまなタイプを「自動的に」区別することが一般的に望ましいと考えられています。 。

あなたのようなケースでは、アクションコードにマップする列挙を使用し、ジェネリックまたは型構築を使用してさまざまなActionサブクラスオブジェクトを構築できるように、属性を列挙値ごとにアタッチして、オーバーロードメソッドが作業。

しかし、それは本当の痛みです。

ソリューションで実現可能な列挙型などの設計オプションがあるかどうかを評価します。そうでない場合は、スイッチを使用してください。

32
Toby

「悪い」switch文は、多くの場合、オブジェクトタイプ(または別の設計ではオブジェクトタイプである可能性があるもの)を切り替えるものです。言い換えれば、ポリモーフィズムによってより適切に処理される可能性があるものをハードコーディングします。他の種類のswitchステートメントは大丈夫かもしれません

Switchステートメントが必要になりますが、必要なのは1つだけです。メッセージを受け取ったら、Factoryオブジェクトを呼び出して適切なメッセージサブクラス(移動、攻撃など)のオブジェクトを返し、次にmessage-> doit()メソッドを呼び出して作業を行います。

つまり、メッセージタイプをさらに追加した場合、変更する必要があるのはファクトリオブジェクトだけです。

18

Strategy パターンが思い浮かびます。

戦略パターンは、アルゴリズムのファミリーを定義し、それぞれをオブジェクトとしてカプセル化し、それらを交換可能にする手段を提供することを目的としています。戦略パターンにより、アルゴリズムを使用するクライアントとは独立してアルゴリズムを変更できます。

この場合、「アルゴリズムのファミリー」はあなたの異なるアクションです。


Switchステートメントについて-「クリーンコード」で、Robert Martinは、型ごとにoneswitchステートメントに制限しようとすると述べています。それらを完全に排除するわけではありません。

その理由は、switchステートメントが [〜#〜] ocp [〜#〜] に準拠していないためです。

17
Oded

メッセージを配列に入れ、アイテムをソリューションキーと照合してメッセージを表示します。

設計パターンの観点からは、特定のシナリオのコマンドパターンを使用できます。 ( http://en.wikipedia.org/wiki/Command_pattern を参照)。

OOPパラダイムでswitchステートメントを繰り返し使用していることに気付いた場合、これはクラスが適切に設計されていない可能性があることを示しています。スーパークラスとサブクラスの適切な設計とかなりの量があるとします。ポリモーフィズムの概要。switchステートメントの背後にあるロジックは、サブクラスで処理する必要があります。

これらのスイッチステートメントを削除する方法と適切なサブクラスを導入する方法の詳細については、Martin Fowlerによるリファクタリングの最初の章を読むことをお勧めします。または、ここで同様のスライドを見つけることができます http://www1.informatik.uni-wuerzburg.de/database/courses/pi2_ss03_dir/RefactoringExampleSlides.pdf 。 (スライド44)

4
TechTravelThink

IMO switchステートメントはbadではありませんが、可能であれば回避する必要があります。 1つの解決策は、キーがコマンドであるMapを使用し、Commandオブジェクトをexecute()メソッドで使用することです。または、コマンドが数値でギャップがない場合はListを使用します。

ただし、通常は、デザインパターンを実装するときにswitchステートメントを使用します。 1つの例は、 Chain of response パターンを使用して、コマンド「id」または「value」が指定されたコマンドを処理することです。 ( Strategy パターンについても触れました。)ただし、あなたのケースでは Command パターンを調べることもできます。

基本的に、OOPでは、手続き型プログラミングパラダイムを使用するswitchブロックに依存する以外のソリューションを使用しようとします。ただし、どちらをいつどのように使用するかは、あなた次第です。個人的には、 Factory パターンなどを使用するときにswitchブロックを頻繁に使用します。


コード編成の定義は次のとおりです。

  • パッケージは、一貫したAPIを持つクラスのグループです(例:多くのフレームワークのCollection AP​​I)
  • クラスは一貫した機能のセットです(例:Mathクラス...
  • aメソッドa機能です。 1つのことと1つのことだけを行う必要があります。 (例:リストに項目を追加するには、そのリストを拡大する必要がある場合があります。その場合、addメソッドは、他のメソッドに依存し、その操作自体を実行しません。これは、コントラクトではないためです。 )

したがって、switchステートメントがさまざまな種類の操作を実行する場合、その定義は「violating」です。一方、デザインパターンを使用するのは、各操作が独自のクラス(独自の機能セット)で定義されているためではありません。

4
Yanick Rochon

コマンドを使用します。アクションをオブジェクトにラップして、ポリモーフィズムに切り替えてもらいます。 C++(shared_ptrは単なるポインタ、またはJava用語での参照です。動的なディスパッチが可能です):

void GameServer::perform_action(shared_ptr<Action> op) {
    op->execute();
}

クライアントは実行するアクションを選択し、実行すると、そのアクション自体をサーバーに送信するため、サーバーは解析を行う必要がありません。

void BlueClient::play() {
    shared_ptr<Action> a;
    if( should_move() ) a = new Move(this, NORTHWEST);
    else if( should_attack() ) a = new Attack(this, EAST);
    else a = Wait(this);
    server.perform_action(a);
}
2
wilhelmtell

買わない。これらのOOP熱狂者は、無限のRAMと驚くべきパフォーマンスを備えたマシンを持っているようです。明らかにinifinite RAM RAM=断片化と、小さなヘルパークラスを継続的に作成および破棄するときのパフォーマンスへの影響を心配する必要があります。「美しいコード」の本の引用を言い換えると、「コンピューターサイエンスのすべての問題で、別のレベルの抽象化で解決される」

必要に応じてスイッチを使用してください。コンパイラーは、それらのコードを生成するのにかなり優れています。

0
James