web-dev-qa-db-ja.com

さまざまなパラメータを持つ戦略パターン

ストラテジーパターンを使用しているときに問題が発生しました。タスクを作成するためのサービスを実装しています。このサービスは、このタスクの責任者も解決します。これを行うにはさまざまな方法があるため、店員の解決は戦略パターンを使用して行われます。重要なのは、すべての戦略が店員を解決するために異なるパラメーターを必要とする可能性があるということです。

例えば:

interface ClerkResolver {
    String resolveClerk(String department);
}

class DefaultClerkResolver implements ClerkResolver {

    public String resolveClerk(String department) {
        // some stuff
    }
}

class CountryClerkResolver implements ClerkResolver {

    public String resolveClerk(String department) {
        // I do not need the department name here. What I need is the country.
    }

}

問題は、すべてのリゾルバーが担当の店員を解決するために異なるパラメーターに依存する可能性があることです。私にとって、これは私のコードの設計上の問題のように聞こえます。また、次のように、戦略に必要なすべての値を保持するためのパラメーターとしてクラスを作成しようとしました。

class StrategyParameter {

   private String department;
   private String country;

   public String getDepartment() ...
}

interface ClerkResolver {
    String resolveClerk(StrategyParameter strategyParameter);
}

しかし、正直なところ、戦略に新しい/異なる引数が必要になるたびにパラメータークラスを変更する必要があるため、このソリューションには満足していません。次に、戦略の呼び出し元は、どの戦略が店員を解決するかわからないため、すべてのパラメーターを設定する必要があります。したがって、すべてのパラメーターを提供する必要があります(ただし、これはそれほど悪くありません)。

繰り返しますが、私にとってこれは私のコードの設計上の問題のように聞こえますが、より良い解決策を見つけることができません。

---編集

このソリューションの主な問題は、タスクを作成するときです。タスクサービスは次のようになります。

class TaskService {

    private List<ClerkResolver> clerkResolvers;

    Task createTask(StrategyParamter ...) {

        // some stuff

       for(ClerkResolver clerkResolver : clerkResolvers) {
          String clerk = clerkResolver.resolveClerk(StrategyParameter...)
          ...
       }

       // some other stuff
    }

}

TaskServiceがいつ使用されるかがわかるように、TaskService自体にはこれらの情報がないため、発信者は店員を解決するために必要な情報、つまり部門名や国を提供する必要があります。

タスクを作成する必要がある場合、店員を解決するために必要であるため、呼び出し元はStrategyParameterを提供する必要があります。繰り返しますが、問題は、発信者がすべての情報を持っていない、つまり、発信者が国についての知識を持っていないということです。彼は部門名しか設定できません。そのため、戦略が店員の解決を処理できるようにするために、インターフェイスに2番目のメソッドを追加しました。

interface ClerkResolver {
    String resolveClerk(StrategyParameter strategyParameter);
    boolean canHandle(StrategyParameter strategyParameter);
}

私を繰り返す危険を冒して、この解決策は私には正しく聞こえません。

ですから、誰かがこの問題のより良い解決策を持っているなら、私はそれを聞いていただければ幸いです。

コメントしてくれてありがとう!

20
Daniel

タスクが実際に何であるかについては、多少の混乱があると思います。私の考えでは、タスクは店員によって行われるものです。そのため、店員のことを知らなくても、タスク自体を作成できます。

そのタスクに基づいて、適切な店員を選ぶことができます。店員へのタスクの割り当て自体は、他の種類のタスクにラップすることができます。したがって、店員を選択するための一般的なインターフェイスは次のようになります。

interface ClerkResolver {
    String resolveClerk(Task task);
}

この種のクラークリゾルバーを実装するために、たとえば、タスクの実際のタイプに基づいた戦略パターンを使用できます。

9
SpaceTrucker

抽象化を別のレベルに移動することで問題が解決されることがあるというSpaceTruckerの提案が本当に気に入りました:)

しかし、元の設計がより理にかなっている場合(仕様の感触に基づいて、あなただけが知ることができます)、私見は次のいずれかを行うことができます:1)「すべてをStrategyParameterにロードする」というアプローチを維持する2)またはこの責任を戦略

オプション(2)の場合、部門/国を推測できる共通のエンティティ(アカウント?顧客?)があると思います。次に、国を検索する「CountryClerkResolver.resolveClerk(StringaccountId)」があります。

IMHOは、コンテキストに応じて、(1)、(2)の両方が正当です。すべてのパラメータ(部門+国)のプリロードが安価であるため、(1)がうまくいくことがあります。合成の「StrategyParameter」をビジネスに直観的なエンティティ(アカウントなど)に置き換えることさえできます。時々(2)私にとってはうまくいくことがあります。 'department'と 'country'が別々の高価なルックアップを必要とした場合。複雑なパラメータで特に注目されるようになります-例:戦略が「顧客満足度」レビューのスコアに基づいて店員を選択する場合、それは複雑な構造であり、より単純な戦略のためにロードするべきではありません。

3
Pelit Mamani

コードが単純なif-else-ifブロックに基づいていると仮定することから始めましょう。

このようなシナリオでも、必要なすべての入力を事前に用意しておく必要があります。それを回避することはできません。

戦略パターンを使用することで、コードの分離を開始します。つまり、基本インターフェースと具体的な実装を定義します。

If-else-ifブロックが必要なため、このデザインだけでは十分ではありません。

この時点で、次の設計変更を確認できます。

  1. ファクトリパターンを使用して、このシステムから利用可能なすべての戦略をロードします。これは、JDKで使用可能な Service Loader パターンなどのメタ情報に基づいている可能性があります。

  2. 使用可能な実装を照会して、指定されたパラメーターの入力セットを処理できるかどうかを確認するための戦略を特定します。これは、canYouResolve(input)!= nullのように単純にすることができます。これを行うことにより、if-else-ifブロックからfor-eachループに変更します。

  3. あなたの場合、デフォルトの実装もあります。したがって、デフォルトの実装がモジュールの一部であり、他の戦略が他のjar(ポイント1からServiceLoaderを介してロードされる)から入ってくるとしましょう。

  4. コードが開始されると、最初に利用可能なすべての戦略を探します。現在のシナリオを処理できるかどうかを尋ねます。それらのどれもそれを処理できない場合は、デフォルトの実装を使用してください。

何らかの理由で、特定の入力を処理できるリゾルバーが複数ある場合は、それらのリゾルバーの優先度を定義することを検討する必要があります。

さて、入力パラメータに来て、これらのパラメータはいくつかの入力オブジェクトから派生できますか?もしそうなら、なぜその入力オブジェクト自体をリゾルバーに送信しませんか。

注:これは、JavaEE ELResolverの動作と非常によく似ています。その場合、コードはELに解決済みのマークを付け、解決が完了したことをルートクラスに通知します。

注:サービスローダーが重すぎると思われる場合は、すべてのMETA-INF/some-file-that-you-likeを検索して、システムで使用可能なリゾルバーを特定してください。

私自身の経験から、ほとんどの場合、手元のユースケースを実現するためにパターンを混合するコードを書くことになります。

これがあなたのシナリオに役立つことを願っています。

2
Harsha R