使用Guice,枚举和静态工厂方法设计工厂框架

时间:2013-07-17 20:56:35

标签: java subclass guice factory enumeration

考虑一种情况,我希望将String data包含一些数据并将其解析为某种对象,例如Animal免责声明:即使很长,这也是一个sscce;我的实际项目与猫的声音没什么关系:)。

要求:

  • 第一个字符表示“动物的类型”。因此C可能会引用abstract class CatD可能会引用abstract class Dog
  • 第二个字符可选地表示“动物的子类型”......除了这些子类型被分组到类别中(就类别而言)。因此,CS可能是ThaiCat extends Cat,参数"Siamese"CK可能是ThaiCat extends Cat,其参数为"Korat"CB可能使用参数AmericaCat extends Cat
  • Bengal
  • 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 - &gt; Cat子类配对。然后将Map<K, Provider<V>>映射绑定到工厂类,并在创建后将data作为方法调用传递给Cat对象
  • 创建一个AssistedInject工厂,让每个枚举的makeCat方法在工厂中调用正确的方法。问题是,我不能将工厂注入Enumeration实例,而Guice建议不要使用静态注入。因此,我必须一直沿着方法链传递我的工厂,这似乎打败了目的。此外,这并没有解决不允许错误的String调用错误的构造函数的问题。
  • 创建手动工厂对象。虽然我不确定工厂应该做多少工作以及枚举(如果有的话)多少工作。

什么是最好的解决方案?

1 个答案:

答案 0 :(得分:1)

你有两个问题,真的:

  1. 如何为Guice提供决定做出何种对象所需的输入
  2. 如何让Guice运行代码以制作正确的对象
  3. 问题1。

    Guice 真的非常希望使用启动时提供的信息在启动时制作一个大对象图。然而,很多强大的功能来自于它能够根据运行时的条件改变其行为 - 因此很多构建在Guice上的框架都可以实现这一点 - Servlet支持具有请求范围,它允许注入servlet请求,等等。

    有三种基本方法可以在创建对象时动态创建对象:

    • Assisted Inject
    • Custom scopes
    • 写一个一次性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实例。如果动物的数量很小且有限,并且Animal对象是无状态的,您可能只是迭代所有可能的组合并在启动时创建所有组合,然后您只是进行简单的查找。或者您可以解析输入并即时做出决定 - 取决于您的需要。

    Enum或Enum

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

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

    如果你真的,真的需要限制一些代码只采用动画的枚举实例,你可以做到这一点,而不必将代码绑定到特定的枚举:

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

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