web-dev-qa-db-ja.com

大きなSwitchステートメント:悪いOOP?

大きなswitchステートメントは、悪いOOPデザインの症状です。過去に、このトピックについて説明している記事を読んだことがあり、代替のOOPベースのアプローチ。通常は、ポリモーフィズムに基づいて、適切なオブジェクトをインスタンス化してケースを処理します。

私は今、TCPソケットからのデータのストリームに基づいた不思議なswitchステートメントがあり、プロトコルが基本的に改行で終了するコマンドで構成され、その後にデータの行が続き、コマンドは100種類のコマンドのいずれかになる可能性があるので、このモンスターの切り替えステートメントをより扱いやすいものに減らす方法を見つけたいと思います。

私が思い出す解決策を見つけるためにグーグルで検索しましたが、残念なことに、最近、Googleは多くの種類のクエリにとって無関係な結果の浪費地になっています。

この種の問題のパターンはありますか?可能な実装に関する提案はありますか?

私が考えていたのは、辞書のルックアップを使用して、コマンドテキストをオブジェクトタイプと照合してインスタンス化することでした。これには、単に新しいオブジェクトを作成し、新しいコマンド/タイプを新しいコマンドのテーブルに挿入するという素晴らしい利点があります。

ただし、これには型爆発の問題もあります。 100の新しいクラスが必要になりました。さらに、それらをデータモデルにクリーンにインターフェースする方法を見つける必要があります。 「1つの真の切り替えステートメント」は本当に進むべき道なのか?

ご意見、ご感想、コメントをいただければ幸いです。

74

コマンドパターン からいくつかの利点を得ることができます。

OOPの場合、動作のバリエーションが十分に小さい場合は、クラスの完全な爆発を回避するために、いくつかの同様のコマンドをそれぞれ1つのクラスに折りたたむことができます(そうです、OOP gurusを聞くことができます)ただし、システムがすでにOOPであり、100以上のコマンドのそれぞれが本当に一意である場合は、それらを一意のクラスにして、継承を利用して共通のものを統合します。

システムがOOPでない場合、OOPこれを追加するだけではありません...単純な辞書ルックアップと関数ポインター、または動的に生成された関数を使用してコマンドパターンを簡単に使用できます。言語に応じて、コマンド名に基づいて呼び出します。その後、論理的に関連付けられた関数を、類似したコマンドのコレクションを表すライブラリにグループ化して、管理しやすい分離を実現できます。この種の実装に適切な用語があるかどうかはわかりません。 ..私は常に、URLを処理するMVCアプローチに基づいた「ディスパッチャー」スタイルと考えています。

33
nezroy

非OO設計の症状としてtwo switchステートメントがあると思います。ここで、switch-on-enum-typeは、抽象インターフェースの異なる実装を提供する複数のタイプに置き換えられる可能性があります。たとえば、次の...

switch (eFoo)
{
case Foo.This:
  eatThis();
  break;
case Foo.That:
  eatThat();
  break;
}

switch (eFoo)
{
case Foo.This:
  drinkThis();
  break;
case Foo.That:
  drinkThat();
  break;
}

...おそらく次のように書き換える必要があります...

IAbstract
{
  void eat();
  void drink();
}

class This : IAbstract
{
  void eat() { ... }
  void drink() { ... }
}

class That : IAbstract
{
  void eat() { ... }
  void drink() { ... }
}

ただし、one switchステートメントis n't imoは、switchステートメントを別のものに置き換える必要があることを示す強力なインジケーターです。

24
ChrisW

コマンドは、100の異なるコマンドのいずれかです。

100種類の方法のうち1つを実行する必要がある場合、100方向のブランチを作成することは避けられません。制御フロー(スイッチ、if-elseif ^ 100)またはデータ(文字列からコマンド/工場/戦略への100要素のマップ)でエンコードできます。しかし、それはそこにあります。

100-wayブランチの結果を、その結果を知る必要のないものから分離しようとすることができます。たぶん、100の異なる方法で十分です。コードが扱いにくくなる場合は、不要なオブジェクトを発明する必要はありません。

16
Jonas Kölker

これは、他の解決策が提示されない限り、大きなスイッチが最良の答えになる数少ないケースの1つだと思います。

3
Loren Pechtel

大きなswitchステートメントについて話すとき、次の2つが頭に浮かびます。

  1. OCPに違反しています。大きな機能を継続的に維持している可能性があります。
  2. パフォーマンスが悪い可能性があります:O(n)。

一方、マップの実装はOCPに準拠しており、O(1)で実行できる可能性があります。

2
quamrana

戦略パターンが見えます。 100種類の戦略があるとしたら...巨大なswitch文は醜いです。すべてのコマンドは有効なクラス名ですか?その場合は、コマンド名をクラス名として使用し、Activator.CreateInstanceで戦略オブジェクトを作成します。

2
Jason Punyon

辞書(Javaでコーディングしている場合はハッシュマップ)を使用できます(これはSteve McConnellによってテーブル駆動開発と呼ばれています)。

1
Nicolas Dorier

問題は大きなswitchステートメントではなく、それに含まれるコードの急増、およびスコープが正しくない変数の乱用であると思います。

自分で1つのプロジェクトでこれを経験しました。メンテナンスができなくなるまで、スイッチに次々とコードが追加されていきました。私の解決策は、コマンドのコンテキスト(名前、パラメーターなど、スイッチの前に収集されたもの)を含むパラメータークラスを定義し、各ケースステートメントのメソッドを作成し、ケースからパラメーターオブジェクトを使用してそのメソッドを呼び出すことでした。

もちろん、完全にOOPコマンドディスパッチャー(リフレクションやJavaアクティベーションなどのメカニズムなど)に基づく)の方がより美しいですが、場合によっては修正したいだけです物事と仕事を成し遂げる;)

1
devio

はい、私は大きなcaseステートメントは自分のコードを改善できる兆候だと思います...通常はよりオブジェクト指向のアプローチを実装することによって。たとえば、switchステートメントでクラスの型を評価している場合、ほとんどの場合、Genericsを使用してswitchステートメントを削除することができます。

0
cyclo

私はあなたがあなたのコードをデータによって駆動するように改善することができると思う一つの方法を示します、例えば、各コードに対してあなたはそれを処理する何か(関数、オブジェクト)にマッチします。リフレクションを使用してオブジェクト/関数を表す文字列をマップし、実行時に解決することもできますが、パフォーマンスを評価するためにいくつかの実験を行うことができます。

0
Otávio Décio

Windowsがアプリケーションメッセージポンプで最初にどのように記述されたかを考えてみてください。吸いました。追加したメニューオプションが多いと、アプリケーションの実行が遅くなります。検索されたコマンドがswitchステートメントの下部に向かってさらに終了したため、応答の待機時間がますます長くなりました。長いswitchステートメント、期間を持つことは受け入れられません。 TCP/IPで受信したリクエストストリームの内容を知らなくても、256個の固有のコマンドを処理できるPOSコマンドハンドラーとしてAIXデーモンを作成しました。ストリームの最初の文字は、関数配列へのインデックスでした。使用されていないインデックスは、デフォルトのメッセージハンドラに設定されています。ログインしてさよならを言う。

0
Robert Achmann

ここで言語アプローチを採用し、文法で関連するデータを使用してコマンドを定義することもできます。次に、ジェネレーターツールを使用して言語を解析できます。そのために Irony を使用しました。あるいは、Interpreterパターンを使用できます。

私の意見では、目標は最も純粋なOOモデルを構築することではなく、柔軟で拡張可能、保守可能で強力なシステムを作成することです。

0
Rine

最近、巨大なswitchステートメントで同様の問題が発生し、ほとんどのsimpleソリューションa Lookup tableと、値を返す関数またはメソッドによって醜いスイッチを取り除きました期待する。コマンドパターンはニースのソリューションですが、クラスが100あるのはいいとは思いません。だから私は次のようなものを持っていました:

switch(id)
    case 1: DoSomething(url_1) break;
    case 2: DoSomething(url_2) break;
    ..
    ..
    case 100 DoSomething(url_100) break;

そして私は次のように変更しました:

string url =  GetUrl(id);  
DoSomthing(url);

getUrlはDBにアクセスして、探しているURLを返すか、100個のURLを保持するメモリ内の辞書にすることができます。巨大な巨大なswitch文を置き換えるときに、これが誰にとっても役立つことを願っています。

0
darmis

この特定の問題を処理する最良の方法:シリアライゼーションとプロトコルは、IDLを使用して、switchステートメントでマーシャリングコードを生成することです。他に使用しようとするパターン(プロトタイプファクトリ、コマンドパターンなど)があるため、コマンドID /文字列とクラス/関数ポインタ間のマッピングを初期化する必要があります。どういうわけか、switchステートメントよりも実行速度が遅くなります。コンパイラーは、switchステートメントに完全なハッシュ検索を使用できます。

0
ididak