web-dev-qa-db-ja.com

スイッチからポリモーフィズムへの適切な再設計(オープン/クローズの原則)

このswitchステートメントを削除しようとして、大きな問題が発生しています。少しコンテキストを優先するために、私は非同期バッチ操作で作業しています。これらの操作は、システム内の任意のエンティティに適用できます。別の名前空間を送信するだけです。

例:

"jobs": [
{
    namespace: "client",
    request: {"id": 10, "name": "john"}
}, 
{
    namespace: "invoice",
    request: {"id": 99, "number": "F20170101"}
}]

これらのジョブを送信した後、サービスは同様のデータを返しますが、次のような応答キーが返されます。

results: [
{
    namespace: "client",
    request: {id: 10, name: "john"},
    response: {success: false}
}, 
{
    namespace: "invoice",
    request: {id: 99, number: "F20170101"},
    response: {success: true, s_id: 3310}
}]

ここから面白い部分が出てきます

応答を処理する必要があり、名前空間に基づいて、データベースレコードを更新するための適切なリポジトリを選択する必要があります。これまでのところ私はこれを持っていますsmelly事:

if (namespace == 'client') {
    customerRepository.doSomething()
} elseif (namespace == 'invoice') {
    invoiceRepository.doSomethingTotallyDifferent(withDiffParams)
}

これは明らかにオープン/クローズの原則に違反しており、switch(実際にはif/elseifスパゲッティのようなもの)を削除してこれを解決する方法を理解できませんが、同じ)。前もって感謝します。

PS:これは特定の言語の質問ではないため、特定の言語にのみ当てはまる回答は提供しないでください。

リポジトリは、共通の基本クラスから派生させる必要があります。baseRepositoryと呼びましょう。このクラスは2つの仮想メソッドを提供する必要があります

  • correspondingNamespace()

  • processReponse(responseKey)

派生クラスのメソッドをオーバーライドすると、correspondingNamespaceは各リポジトリの正しい名前空間を返す必要があります。たとえば、customerRepositoryの「client」。 processReponseは、doSomethingの呼び出しや、異なるパラメーターを使用したdoSomethingTotallyDifferentの呼び出しなど、個々のビジネスロジックに委任する必要があります。

これで、疑似コードで、事前に使用可能なリポジトリーで辞書を簡単に初期化できます。

for each repo in [customerRepository, invoiceRepository]:
     repoDict[repo.correspondingNamespace()]=repo

そして、応答処理は(おおよそ)次のようになります。

  repoDict[responseKey.namespace].processReponse(responseKey)

コメント:これらのメソッドをリポジトリに直接追加したくない場合は、戦略クラスの個別の階層を使用して、2つのメソッドをそこに配置できます。 customerResponseStrategyからinvoiceResponseStrategyresponseStrategyを派生させ、各responseStrategyに対応するリポジトリへの参照を保持させます。それは次のようなものにつながります

 for each respStrat in 
        [new customerResponseStrategy(customerRepository),
         new invoiceResponseStrategy(invoiceRepository)]:
         responseStrategyDict[respStrat .correspondingNamespace()]=respStrat 

そして

  responseStrategyDict[responseKey.namespace].processReponse(responseKey)
5
Doc Brown

私がプロジェクトで何度も行ったことは、LOTの新機能のリクエストに役立ちました。

interface JobResponseWorker {
    bool canProcess(namespace);
    void process(response);
}

class ClientResponseWorker implements JobResponseWorker {

    bool canProcess(namespace) {
        return namespace == "client"
    }
    void process(response) {
        //... whatever you want to do here
    }
}

class InvoiceResponseWorker implements JobResponseWorker {
    bool canProcess(namespace) {
        return namespace == "invoice"
    }
    void process(response) {
        //... whatever you want to do here
    }
}

workers = [new ClientResponseWorker(), new InvoiceResponseWorker()]

//your main logic
void processResponse(namespace, request, response) {

    for (JobResponseWorker worker in workers) {
        if (worker.canProcess(namespace)){
            worker.process(response)
        }
    }
}

このように、メインコードのほとんどは変更のために閉じられています。新しいワーカーの場合は、新しいコードを追加し(拡張用にオープン)、配列に別のオブジェクトを追加するだけです(古いコードには追加のロジックはありません)。

3
Emerson Cardoso

ここで探しているのは Strategy パターンで記述される種類のアプローチだと思います。簡単に言えば、名前空間をクラス/オブジェクトに変換する必要があります。あなたの例では、ClientクラスとInvoiceクラスがあります。それぞれにdoTheThing()メソッドがあります。

残っているのは、使用するタイプを決定する何らかのスイッチまたはマップが必要であることです。これは、このような単純な例では少し無意味に見えるかもしれませんが、操作とクラスの数が増えると、両方のコードが大幅に簡略化されて、はるかに理解しやすくなります。また、他のタイプの既存のロジックを変更することなく、新しいタイプを追加できるようになります。

0
JimmyJames

この差別を行わなければならない場所がコード内に1つしかない限り、それを維持する必要があります。しかし、namespace値に異なる動作を適用する必要がある場所がさらにある場合は、「ポリモーフィック」アプローチを使用する必要があります。

問題は、ある時点でnamespace値に属する実装を選択する必要があるため、「ポリモーフィック」なアプローチでは差別化も行う必要があることです。

0
Timothy Truckle