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


10

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

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

どう思いますか?


2
現在のコードの実際の問題は何ですか?
フィリップケンドール

これらの変更の1つを行う必要がある場合はどうなりますか?switchケースを追加して、複雑なシステムで既存のメソッドを呼び出す必要がありますか、それともメソッドとその呼び出しの両方を発明する必要がありますか?
キリアンフォス2017

@KilianFothメンテナンス開発者としてこのプロジェクトを継承しており、まだ変更を加える必要はありません。ただし、すぐに変更するので、今すぐリファクタリングしたいと思います。しかし、あなたの質問に答えるために、後者に対してはい。
Kaushik Chakraborty 2017

2
何が起こっているのかを凝縮した例を示す必要があると思います。
whatsisname 2017

1
@KaushikChakraborty:次に、メモリから例を構成します。250以上のケースのuberスイッチが適切な場合もあれば、スイッチの数が少なくてもスイッチが悪い場合もあります。悪魔は詳細にあり、詳細はありません。
whatsisname 2017

回答:


13

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

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

戦略にリファクタリングするための適切なケースを作成していません。ちなみにリファクタリングには名前があります。これは、「条件付きをポリモーフィズムに置き換える」と呼ばれます

私はc2.comからいくつかの適切なアドバイスを見つけました:

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

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

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

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


私はあなたが言っていることに同意します。コードには多くの冗長性があり、多くの場合は必要もないかもしれませんが、ざっと見ただけではそうは見えません。いずれの場合も、複数のシステムを呼び出して結果を集計し、呼び出し元のコードに戻るメソッドを呼び出します。すべてのクラスは自己完結型であり、1つの仕事を行います。私は、ケースの数を減らすために、高度な結束の原則に違反するのではないかと心配しています。
Kaushik Chakraborty 2017

2
私は高い結束に違反することなく50を得ることができ、物事を自己完結させておくことができます。1つの数字でそれを行うことはできません。2、5、5が必要です。そのため、ファクタリングと呼ばれています。真剣に、あなたのアーキテクチャ全体を見て、あなたがいるこの50ケースの地獄からの道を見つけることができないかどうかを確かめてください。リファクタリングは、悪い決定を取り消すことについてです。新しい形でそれらを永続化しない。
candied_orange 2017

ここで、このリファクタリングを使用して50を削減する方法を確認できる場合は、それを試してください。Doc Brownsのアイデアを活用するには:マップのマップは2つのキーを取ることができます。考えるべきこと。
candied_orange 2017

1
Candiedのコメントに同意します。問題はswitchステートメントの50ケースではありません。問題は、50のオプション間で決定する必要がある関数を呼び出す原因となっている、より高いレベルのアーキテクチャ設計です。私はいくつかの非常に大きく複雑なシステムを設計しましたが、そのような状況に追い込まれたことはありません。
ダンク

@Candied "同じ入力で別のどこかに別のswitchステートメントを作成する必要がある場合、このリファクタリングを使用します。プロジェクトの最初の承認、検証、CRUD手順、その後dao。したがって、すべてのレイヤーで、同じ入力(整数)にスイッチケースがありますが、auth、validなどのさまざまな機能を実行します。それで、メソッドが異なるタイプごとに1つのクラスを作成する必要がありますか?私たちのケースは、「同じ入力で同じスイッチを繰り返す」ことであなたが言おうとしていることに当てはまりますか?
Siddharth Trikha

9

いくつかのコード行が次のようになっているコードのいくつかの関数で初期化される戦略オブジェクトのみのマップ

     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;

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

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

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

したがって、はい、いくつかの利点があります。これは確かに、SOLIDコードを増やすためのステップです。しかし、それがリファクタリングに見合うものである場合、それはあなた自身またはあなたのチームが自分で決定しなければならないことです。ディスパッチコードの変更、初期化ロジックの変更を予期してswitchおらず、の可読性が実際の問題ではない場合、リファクタリングはそれほど重要ではない可能性があります。


私はすべてのスイッチをポリモーフィズムに盲目的に置き換えることに消極的ですが、マップを使用して、Doc Brownがここで提案する方法は、過去には非常にうまく機能していると言います。あなたが同じインターフェイスを実装する場合は交換してくださいDoit1Doit21となど、Doit多くの異なる実装を持つメソッド。
candied_orange 2017

また、キーとして使用する入力記号のタイプを制御できるdoTheThing()場合は、入力記号のメソッドを作成することで、さらに一歩先に進むことができます。その後、マップを維持する必要はありません。
Kevin Krumwiede 2017

1
@KevinKrumwiede:提案することは、整数の代わりに、戦略オブジェクト自体をプログラム内で単に渡すことを意味します。ただし、プログラムが外部データソースから整数を入力として受け取る場合、少なくともシステムの1か所に整数から関連する戦略へのマッピングが必要です。
Doc Brown

Doc Brownの提案を拡張します。この方法を選択する場合は、戦略オブジェクトを作成するためのロジックを含むファクトリを作成することもできます。そうは言っても、CandiedOrangeによって提供された答えは私にとって最も理にかなっています。
Vladimir Stokic 2017

@DocBrown「入力シンボルのタイプを制御できる場合」で私が得ていたのはそれです。
Kevin Krumwiede 2017

0

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

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

呼び出し

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

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

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

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;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.