0

String dataをいくつかのデータとともに取得し、それを特定の種類のオブジェクトに解析するシナリオを考えてみましょうAnimal免責事項:長いですが、これは sscce です。私の実際のプロジェクトは、猫の音とはほとんど関係がありません:)

要件:

  • 最初の文字は「動物の種類」を示します。だからCを参照するかもしれませんしabstract class Cat、 を参照するDかもしれませんabstract class Dog
  • 2 番目の文字は、オプションで「動物のサブタイプ」を示します... ただし、これらのサブタイプはカテゴリにグループ化されます (クラスに関する限り)。したがって、 aは引数付きであるCS可能性がありThaiCat extends Cat、引数付き"Siamese"であるCK可能性があり、ThaiCat extends Cat引数付き"Korat"CBある可能性がAmericaCat extends CatありますBengal
  • data Stringは他の情報があります。たとえば、動物の名前が含まれている場合があります。このデータを解析する方法について心配する必要はありません。このコードは 間で共有されますabstract class(すべてのサブタイプに当てはまるものを解析できCat、サブクラスは残りの必要なデータを解析します)。

最初の解決策は、これから始めます:

public enum AnimalType {
  CAT ('C') { Animal makeAnimal(String data) { return CatType.makeCat(data); },
  DOG ('D') { Animal makeAnimal(String data) { return DogType.makeDog(data); };
  private char type;
  public char getType() { return type; }
  private AnimalType(char type) { this.type = type; }
  abstract Animal makeAnimal(String data);

  private static Map<Character, AnimalType> animalMap = new HashMap<>();
  static {
    for(AnimalType currentType : AnimalType.values()) {
      animalMap.put(currentType.getType(), currentType());
    }
  }
  public static Animal makeAnimal(String data) {
    return animalMap.get(data.charAt(0)).makeAnimal(data);
  }
}

public enum CatType {
  BENGAL ('B') { Cat makeCat(String data) { return new AmericaCat(data, this) },
  RAGDOLL ('R') { Cat makeCat(String data) { return new AmericaCat(data, this) },
  KORAT ('K') { Cat makeCat(String data) { return new ThaiCat(data, this) },
  SIAMESE ('S') { Cat makeCat(String data) { return new ThaiCat(data, this) };

  private char type;
  public char getType() { return type; }
  private CatType(char type) { this.type = type; }
  abstract Cat makeCat(String data, CatType type);

  private static Map<Character, CatType> catMap = new HashMap<>();
  static {
    for(CatType currentType : CatType.values()) {
      catMap.put(currentType.getType(), currentType());
    }
  }
  static Cat makeCat(String data) {
    return catMap.get(data.charAt(1)).makeCat(data);
  }
}

これはすべてうまくいっています。高速でクリーンで、適切なコード委任などである必要があります。ただし。では、突然動物に依存関係が生じたらどうなるでしょうか (私は Guice を使用しています)。動物の鳴き声を含むライブラリがあり、できるようにしたいとします。animal.speak()サウンド オブジェクトを呼び出す機能は、Animal.

ここに私が考えたいくつかのことがあります:

  • ->サブクラスのペアリングMapBinderをセットアップするために使用します。次に、マップをファクトリ クラスにバインドし、作成後にメソッド呼び出しとしてオブジェクトに渡します。EnumCatMap<K, Provider<V>>dataCat
  • AssistedInject ファクトリを作成し、各列挙のmakeCatメソッドがファクトリ内の正しいメソッドを呼び出すようにします。問題は、Enumeration インスタンスにファクトリを注入できないことです。Guice は静的注入を使用しないことを推奨しています。したがって、メソッド チェーンのずっと下まで自分のファクトリを渡す必要がありますが、これは目的に反しているように見えます。また、これは、間違ったコンストラクターが間違った文字列によって呼び出されることを許可しないという問題を解決しません。
  • 手動ファクトリ オブジェクトを作成します。ただし、ファクトリがどの程度の作業を行う必要があり、列挙型 (存在する場合) がどの程度の作業を行うべきかはわかりません。

最善の解決策は何ですか?

4

1 に答える 1

1

ここには 2 つの問題があります。

  1. 作成するオブジェクトの種類を決定するために必要な入力を Guice に与える方法
  2. Guice にコードを実行させて正しいオブジェクトを作成する方法

問題1。

Guiceは、起動時に提供される情報を使用して、起動時にオブジェクトの大きなグラフを作成したいと考えています。それでも、それを強力にする多くのことは、実行時の条件に応じて動作を変える能力を与えることから来ています.Guice上に構築された多くのフレームワークは、それを可能にするために何かを行います.サーブレットサポートには、サーブレットリクエストを可能にするリクエストスコープがあります.注射する、など。

オブジェクトの作成時に使用するために、オンザフライで作成されたオブジェクトを Guice に提供するには、次の 3 つの基本的な方法があります。

  • 補助注入
  • カスタム スコープ
  • Provider<Animal>何らかの方法で関連データを取得する1 回限りのコードを作成し (通常ThreadLocalはカスタム スコープを使用して、通常はこのパターンを一般化します)、正しいオブジェクトを作成します。

問題2。

入力が実行時にオンザフライで提供され、適切なオブジェクトを作成するファクトリまたはプロバイダーがあると仮定すると、そのオブジェクトをコードに与えるように Guice を説得するか、代替手段を作成する必要があります。データを取得するために必要な情報のパス。

通常の方法は aを使用ThreadLocalします。つまり、 an のインスタンス化をトリガーする可能性のある呼び出しを行う前に、解析したい文字列を含むようにAnimalを設定します。ThreadLocal何かが実際に必要な場合は、解析コードが呼び出されます。不快な使用を見つけた場合は、ThreadLocalScope を実装できます (または、上記のリンクのようなライブラリを使用できます)。ただし、通常は内部を使用するだけThreadLocalです。

以下は、そのすべてがどのように見えるかの簡単な例です。

public class App {
  public interface Animal {
  }
  private static class Cat implements Animal {
  }
  public static void main(String[] args) {
    ThreadLocal<String> theData = new ThreadLocal<>();
    MyModule module = new MyModule(theData);
    Injector inj = Guice.createInjector(module);
    // Try a test run
    theData.set("Cat thing");
    try {
      Animal animal = inj.getInstance(Animal.class);
      assert animal instanceof Cat;
      System.out.println("Got " + animal);
    } finally {
      theData.remove();
    }
  }

  private static class MyModule extends AbstractModule {
    private final ThreadLocal<String> data;
    public MyModule(ThreadLocal<String> data) {
      this.data = data;
    }

    @Override
    protected void configure() {
      bind(new TypeLiteral<ThreadLocal<String>>() {
      }).toInstance(data);
      bind(Animal.class).toProvider(AnimalProvider.class);
    }
  }

  private static class AnimalProvider implements Provider<Animal> {
    private final ThreadLocal<String> data;
    @Inject
    public AnimalProvider(ThreadLocal<String> data) {
      this.data = data;
    }

    public Animal get() {
      String providedAtRuntime = data.get();
      assert providedAtRuntime != null;
      switch (providedAtRuntime.charAt(0)) {
        case 'C':
          return new Cat();
        // ...
        default:
          throw new IllegalArgumentException(providedAtRuntime);
      }
    }
  }
}

最後に考慮すべきことは、Animal インスタンスがどのように作成されるかです。動物の数が少なく有限であり、Animal オブジェクトがステートレスである場合、考えられるすべての組み合わせを反復処理し、起動時にそれらすべてを作成してから、単純なルックアップを実行するだけです。または、入力を解析して、オンザフライでその決定を下すこともできます-必要なものによって異なります。

列挙するか列挙しないか

このようなものに列挙型を使用しないことをお勧めします-遅かれ早かれ、Animal別のラッパーを実装してそれAnimalに委任するか、または同様のものを実装したいと思うでしょう。また、列挙型インスタンスをオンザフライで作成することはできません。

代わりにできることは、 Animalインターフェイスと、そのインターフェイスを実装する列挙型を使用することです。これにより、インターフェイスの柔軟性が得られ、一般的なケースで列挙型を使用できます。列挙。

Animal の列挙型インスタンスのみを取得するようにコードを制限する必要がある場合は、そのコードを特定の列挙型に結び付けずにそれを行うことができます。

public <A extends Animal & Enum<A>> void foo(A animal) { ... }

これにより、将来新しい列挙型で再利用できるコードを記述しながら、列挙型のすべての利点が得られます。

于 2013-07-17T22:54:42.940 に答える