web-dev-qa-db-ja.com

複数のスイッチケースを持つアプリケーションをリファクタリングするにはどうすればよいですか?

整数を入力として受け取り、その入力に基づいて異なるクラスの静的メソッドを呼び出すアプリケーションがあります。新しい番号が追加されるたびに、別のケースを追加して、異なるクラスの異なる静的メソッドを呼び出す必要があります。現在、スイッチには50のケースがあり、別のケースを追加する必要があるたびに震えます。これを行うより良い方法はありますか?.

私はいくつか考えて、このアイデアを思いつきました。私は戦略パターンを使います。スイッチケースを持つ代わりに、キーが入力整数である戦略オブジェクトのマップがあります。メソッドが呼び出されると、オブジェクトが検索され、オブジェクトの汎用メソッドが呼び出されます。このようにして、switch case構文の使用を回避できます。

どう思いますか?

10

現在、スイッチには50のケースがあり、別のケースを追加する必要があるたびに震えます。

私はポリモーフィズムが大好きです。 SOLIDが大好きです。純粋なオブジェクト指向プログラミングが大好きです。彼らが独断的に適用されるため、私はこれらが悪い担当者を与えられるのを見たくありません。

戦略にリファクタリングするための適切なケースを作成していません。ちなみにリファクタリングには名前があります。それは 条件付きを多態性で置き換える と呼ばれます。

c2.com から関連するアドバイスを見つけました。

同じまたは非常に類似した条件テストが頻繁に繰り返される場合にのみ意味があります。単純でめったに繰り返されないテストの場合、単純な条件を複数のクラス定義の冗長性で置き換え、条件付きで必要なアクティビティを実際に必要とするコードからこれまでのところすべてを移動すると、コード難読化の教科書の例が得られます。独断的な純度よりも明快さを優先します。 -DanMuller

50ケースのスイッチがあり、代わりに50個のオブジェクトを作成します。ああ、50行のオブジェクト構築コード。これは進歩していません。何故なの?このリファクタリングは、数を50から減らすために何もしないためです。同じリ入力を別の場所に作成する必要がある場合は、このリファクタリングを使用します。 100を50に戻すため、このリファクタリングが役立ちます。

あなたが持っている唯一のものであるように「スイッチ」について言及している限り、私はこれをお勧めしません。現在リファクタリングから得られる唯一の利点は、一部のgoofballが50ケーススイッチをコピーして貼り付ける可能性が減ることです。

私がお勧めするのは、除外できる共通点について、これらの50のケースを注意深く調べることです。 50って意味?本当に?あなたは本当に多くのケースが必要ですか?ここで多くのことをしようとしているのかもしれません。

13
candied_orange

コードのいくつかの関数で初期化される戦略オブジェクトのみのマップ。

_     myMap.Add(1,new Strategy1());
     myMap.Add(2,new Strategy2());
     myMap.Add(3,new Strategy3());
_

あなたとあなたの同僚は、より均一な方法で別々のクラスで呼び出される関数/戦略を実装する必要があります(戦略オブジェクトはすべて同じインターフェースを実装する必要があるため)。そのようなコードは多くの場合よりも少し包括的です

_     case 1:
          MyClass1.Doit1(someParameters);
          break;
     case 2:
          MyClass2.Doit2(someParameters);
          break;
     case 3:
          MyClass3.Doit3(someParameters);
          break;
_

ただし、新しい番号を追加する必要がある場合でも、このコードファイルを編集する負担から解放されません。このアプローチの本当の利点は別のものです。

  • マップの初期化は、実際にはcalls特定の番号に関連付けられた関数であるディスパッチコードから分離され、後者にはnotが50回の繰り返しが含まれています。 myMap[number].DoIt(someParameters)のようになります。そのため、このディスパッチコードは、新しい番号が到着するたびに触れる必要がなく、Open-Closedの原則に従って実装できます。さらに、ディスパッチコード自体を拡張する必要がある場合、50か所を変更する必要はなく、1か所だけを変更する必要があります。

  • マップの内容は実行時に決定されるのに対し(switchコンストラクトの内容はコンパイル前に決定されます)、これにより、初期化ロジックをより柔軟または拡張可能にする機会が得られます。

ですから、はい、いくつかの利点があります。これは確かに、より多くのSOLIDコードへのステップです。ただし、リファクタリングに見返りがある場合、それはあなたまたはチームが自分で決定する必要があるものです。ディスパッチコードが変更されず、初期化ロジックが変更され、switchの可読性が実際の問題ではない場合は、リファクタリングはそれほど重要ではない可能性があります。

9
Doc Brown

@ DocBrownによる回答 で概説されている戦略に強く賛成です。

答えの改善を提案します。

呼び出し

 myMap.Add(1,new Strategy1());
 myMap.Add(2,new Strategy2());
 myMap.Add(3,new Strategy3());

配布できます。同じファイルに戻って別の戦略を追加する必要はありません。これは、Open-Closedの原則にさらに準拠しています。

ファイルStrategy1.cppにStrategy1を実装するとします。次のコードブロックを含めることができます。

namespace Strategy1_Impl
{
   struct Initializer
   {
      Initializer()
      {
         getMap().Add(1, new Strategy1());
      }
   };
}
using namespace Strategy1_Impl;

static Initializer initializer;

すべてのStategyN.cppファイルで同じコードを繰り返すことができます。ご覧のとおり、これはコードの繰り返しになります。コードの重複を減らすには、すべてのStrategyクラスがアクセスできるファイルに入れることができるテンプレートを使用できます。

namespace StrategyHelper
{
   template <int N, typename StrategyType> struct Initializer
   {
      Initializer()
      {
         getMap().Add(N, new StrategyType());
      }
   };
}

その後、Strategy1.cppで使用する必要があるのは次のものだけです。

static StrategyHelper::Initializer<1, Strategy1> initializer;

StrategyN.cppの対応する行は次のとおりです。

static StrategyHelper::Initializer<N, StrategyN> initializer;

具体的なStrategyクラスのクラステンプレートを使用することにより、テンプレートの使用を別のレベルに引き上げることができます。

class Strategy { ... };

template <int N> class ConcreteStrategy;

そして、Strategy1の代わりにConcreteStrategy<1>を使用します。

template <> class ConcreteStrategy<1> : public Strategy { ... };

ヘルパークラスを変更してStrategysを登録します。

namespace StrategyHelper
{
   template <int N> struct Initializer
   {
      Initializer()
      {
         getMap().Add(N, new ConcreteStrategy<N>());
      }
   };
}

Strateg1.cppのコードを次のように変更します。

static StrategyHelper::Initializer<1> initializer;

StrategN.cppのコードを次のように変更します。

static StrategyHelper::Initializer<N> initializer;
0
R Sahu