17

最近、私は別のプログラマーと、「if」ステートメントでいっぱいの巨大な (1000 行) メソッドをリファクタリングする最善の方法について話し合っていました。

コードは Java で書かれていますが、この問題は C# などの他の言語でも発生する可能性があると思います。

この問題を解決するために、彼は一連の責任パターンを使用することを提案しました。彼は、ベースの「Handler」クラスを持つことを提案しました。次に、「Handler1」、「Handler2」などは「Handler」を拡張します。
次に、ハンドラーには「getSuccessor」メソッドがあり、null (チェーンの最後の場合) またはチェーンの次のハンドラーを返します。
次に、「handleRequest(Request)」関数が Request を処理するか、それをチェーンの次のチェーンに渡します。前の解決策がどれも機能しない場合は、null を返すか、例外をスローします。
チェーンに新しいハンドラを追加するために、コーダーはチェーンの最後の要素に移動し、新しい要素があることを伝えます。何かを行うには、チェーンの最初の要素で handleRequest を呼び出すだけです。

この問題を解決するために、別のアプローチを使用することを提案しました。
前の方法と同じように、「Handler1」、「Handler2」を含む基本「Handler」クラスも必要です。
ただし、「getSuccessor」メソッドはありません。代わりに、ハンドラーのリスト (Vector、ArrayList、またはこの場合に最適なもの) を持つ Collection クラスを使用します。
handleRequest 関数は引き続き存在しますが、呼び出しを次のハンドラーに伝達しません。リクエストを処理するか、null を返すだけです。
リクエストを処理するには、次を使用します

for(Handler handle : handlers){
    result = handle.handleRequest(request);
    if(result!=null) return result;
}
throw new CouldNotParseRequestException(); //just like in the other approach

または、コードの重複を防ぐために、「parseRequest(request)」メソッドをコレクション クラスに追加することもできます。新しいハンドラーを追加するには、コレクション コンストラクター (または static{} ブロック、または同等のもの) に移動し、単純にコード「addHandler(new Handler3());」を追加します。

このアプローチでは、責任連鎖のどのような利点が欠けていますか? どの方法が最適ですか (最適な方法があると仮定して) ? なんで?各設計方法が引き起こす潜在的なバグや問題は何ですか?

コンテキストが必要な人のために、元のコードは次のようになります。

if(x instanceof Type1)
{
//doSomething1
} else if(x instanceof Type2)
{
//doSomething2
}
//etc.
4

4 に答える 4

9

どのアプローチが最適かは、ハンドラーが何をしたいかによって異なります。

ハンドラーが自分でリクエスト リクエストを完全に処理できる場合、あなたのアプローチは問題ありません。ハンドラーは他のハンドラーへの参照を持たないため、ハンドラー インターフェイスが単純になります。Chain of Responsibility の標準実装とは異なり、チェーンの途中でハンドラーを追加または削除できます。実際、リクエストのタイプに応じて、さまざまなチェーンを構築することを選択できます。

あなたのアプローチの問題の 1 つは、ハンドラーが要求に対して前処理または後処理を実行できないことです。この機能が必要な場合は、Chain of Responsibility の方が適しています。CoR では、ハンドラーはチェーンの次のハンドラーへの委任を担当するため、ハンドラーは、チェーンの次のハンドラーからの応答を変更または置換するなど、前処理および/または後処理を行うことができます。このように、CoR は Decorator と非常によく似ています。意図が違うだけです。

CoR では、ハンドラーはチェーンの次のアイテムへの参照を保持するため、チェーンの途中でアイテムを追加または削除することはできません。チェーンの途中でアイテムを追加または削除できるようにする CoR のバリエーションは、フィルター チェーンです (たとえば、javax.servlet.FilterChainを参照)。

あなたが示したコード例は、オブジェクトのタイプに基づいて異なる動作をする一連の「if」ステートメントでした。クリーンアップしているコードでこれが一般的である場合は、要求タイプから必要なハンドラーへのマップを単純に作成できます。

「if」ステートメントを削除するもう 1 つの方法は、継承です。実行する必要のある動作があり、Web サーバー用に 1 つのバリエーションがあり、SOAP サーバー用に別のバリエーションがあった場合、それぞれが RequestHandler を拡張する WebServerRequestHandler と SoapServerRequestHandler を持つことができます。継承の利点は、両方のタイプのリクエストに共通するロジックを配置する明確な場所があることです。欠点は、Java には多重継承がないため、1 次元の問題しかモデル化できないことです。

于 2009-06-28T20:34:19.127 に答える
7

私はそれらの後継者よりもあなたのコレクションのアイデアが好きです。この一連のハンドラーを簡単かつ明確に操作できます。コレクション インターフェイスはよく知られており、リストを反復処理する方法やその他の方法を誰もが理解しています。

友人が提案したこの後継の方法を使用する場合は、非常に深い再帰に陥らないように注意してください (プラットフォームがテール コールをサポートしていない限り、JVM がそれをサポートしているかどうかはわかりません)。

コレクションにメソッドを追加することはお勧めしません。理解しにくく、変更しにくい、はるかに複雑な設計が得られます。ハンドラーのセットを保存することと、一連の責任としてこのハンドラーを解釈することです。コレクションを反復処理してリクエストを処理するメソッドは、コレクション ハウスキーピング メソッドよりも高いレベルの抽象化であるため、コレクション インターフェイスに属すべきではありません。

于 2009-06-28T18:15:20.067 に答える
0

Chain of Responsibility があなたの答えなのか、GoF が適用されるのかさえわかりません。訪問者は正しいことかもしれません。確かな情報が不足しています。

あなたの問題は、古き良きポリモーフィズムで処理できる可能性があります。または、キーを使用して適切なハンドラー オブジェクトを選択する Map かもしれません。

「可能な限り簡単なことをする」ことを心に留めておいてください。コンプレックスが必要だと自分自身に証明するまで、コンプレックスに飛びつかないでください。

私は最近、この考えを促進するAnti-IF キャンペーンについて読みました。ここでは非常に適切に聞こえます。

于 2009-06-28T18:09:28.017 に答える