0

考虑一个场景,我想获取String data一些数据并将其解析为某种对象,比如Animal. 免责声明:尽管它很长,但这是一个 sscce;我的实际项目与猫的声音没什么关系:)

要求:

  • 第一个字符表示“动物的类型”。所以C可以指abstract class Cat,也D可以指abstract class Dog
  • 第二个字符可选地表示“动物的亚型”......除了这些亚型被分组(就类别而言)。所以 aCS可能是ThaiCat extends Catwith argument"Siamese"并且CK可能是 a ThaiCat extends Catwith argument"Korat"并且CB可能是AmericaCat extends Catwith argumentBengal
  • 里面还有data String其他信息。例如,它可能具有 Animal 的名称。不要担心如何解析这些数据,这段代码将在它们之间共享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设置Enum->Cat子类配对。然后将Map<K, Provider<V>>map绑定到一个工厂类中,并在创建后作为方法调用data传入对象Cat
  • 创建一个 AssistedInject 工厂,并让每个枚举的makeCat方法调用工厂中正确的方法。问题是,我无法将工厂注入 Enumeration 实例,Guice 建议不要使用静态注入。因此,我必须沿着方法链一路传递我的工厂,这似乎违背了目的。此外,这并不能解决不允许错误的 String 调用错误的构造函数的问题。
  • 创建手动工厂对象。虽然我不确定工厂应该完成多少工作以及枚举(如果有的话)应该完成多少工作。

最好的解决方案是什么?

4

1 回答 1

1

你有两个问题,真的:

  1. 如何为 Guice 提供决定制作哪种对象所需的输入
  2. 如何让 Guice 运行代码以生成正确的对象

问题1。

Guice真的很想使用在启动时提供的信息在启动时制作一个对象的大图。然而,使它强大的许多原因来自于赋予它根据运行时的条件改变其行为的能力——所以很多基于 Guice 构建的框架都做了一些事情来实现这一点——Servlet 支持有它的请求范围,它允许一个 servlet 请求被注入,等等。

有三种基本方法可以将动态创建的对象提供给 Guice 在创建对象时使用:

  • 辅助注射
  • 自定义范围
  • 写一个一次性的Provider<Animal>,以某种方式获取相关数据(通常使用ThreadLocal- 自定义范围通常是这种模式的概括)并创建正确的对象

问题 2。

假设输入是在运行时动态提供的,并且假设您有一个工厂或提供者将创建正确的对象,您需要说服 Guice 将该对象提供给您的代码,或者您需要创建一些替代方案获取数据的信息路径。

通常的方法是使用ThreadLocal- 即在进行可能触发实例化的调用之前,Animal您将设置ThreadLocal以包含要解析的字符串;如果某些东西确实需要,您的解析代码将被调用。如果您发现使用ThreadLocal令人反感,您可以实现 Scope (或使用一个库,就像上面链接的那样) - 但通常它只是使用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 实例。如果动物的数量很小且有限,并且动物对象是无状态的,您可能只是迭代所有可能的组合并在启动时创建所有它们,然后您只是进行简单的查找。或者您可以解析输入并即时做出决定 - 取决于您的需要。

枚举还是不枚举

我建议不要对这些东西使用 Enums - 你迟早会发现你想要实现一个Animalwhich 包装另一个Animal并委托给它,或者类似的东西,你不能即时创建 enum 实例。

你可以做的是拥有一个 Animal接口,然后是一个实现该接口的枚举 - 这样你就可以获得接口的灵活性并且可以在常见情况下使用枚举 - 只需将所有代码写入接口,而不是枚举。

如果你真的,真的需要限制一些代码只使用 Animal 的枚举实例,你可以在不将该代码绑定到特定枚举的情况下做到这一点:

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

这为您提供了枚举的所有好处,同时仍然编写了可以在将来在新枚举上重用的代码。

于 2013-07-17T22:54:42.940 回答