当前位置: 首页 > 知识库问答 >
问题:

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

秦天宇
2023-03-14

考虑一个场景,我想用一些数据获取String数据,并将其解析为特定类型的对象,例如Animal。免责声明:虽然很长,但这是一个sscce;我的实际项目与猫的声音关系不大:)

要求:

  • 第一个字符表示“动物的类型”。所以 C 可能指抽象类 CatD 可能指抽象类 Dog
  • 第二个字符可选地表示“动物的亚型”...除了这些子类型被分组到类别中(就类而言)。所以CS可能是ThaiCat扩展Cat与参数“Siamese”,CK可能是ThaiCat扩展Cat与参数“Korat”CB可能是AmericaCat扩展Cat与参数孟加拉
  • 数据字符串中包含其他信息。例如,它可能具有动物的名称。不用担心如何解析这些数据,这段代码将在抽象类之间共享(抽象类可以解析所有 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)呢?假设我有一个带动物声音的图书馆,我想做<code>动物。speak(),调用声音对象的功能封装在Animal中。

以下是我考虑过的一些事情:

  • 使用 MapBinder 设置枚举 -

最好的解决方案是什么?

共有1个答案

符修杰
2023-03-14

你在这里有两个问题,真的:

    < li >如何为Guice提供决定制作何种对象所需的输入信息 < li >如何让Guice运行代码以生成正确的对象

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

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

  • 辅助注入
  • 自定义范围
  • 编写一次性提供程序

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

通常的方法是使用< code>ThreadLocal,即在进行可能触发< code>Animal实例化的调用之前,您应该设置< code>ThreadLocal来包含您想要解析的字符串;如果某样东西真的需要它,就会调用您的解析代码。如果你觉得使用< code>ThreadLocal令人不快,你可以实现Scope(或者使用一个这样做的库,就像上面链接的那个)——但是通常它只是在幕后使用< code>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);
      }
    }
  }
}

最后要考虑的是如何创建动物实例。如果动物的数量很少且有限,并且动物对象是无状态的,您可以迭代所有可能的组合并在启动时创建所有这些组合,然后您只需进行简单的查找。或者您可以解析输入并即时做出决定——取决于您的需要。

我建议不要对这种东西使用枚举——你迟早会发现你想要实现一个< code>Animal来包装另一个< code>Animal并委托给它,或者类似的东西,并且你不能动态地创建枚举实例。

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

如果你真的需要限制一些代码只接受 Animal 的枚举实例,你可以这样做,而无需将该代码绑定到特定的枚举:

公共

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

 类似资料:
  • 工厂-创建对象而不向客户机公开实例化逻辑,并通过公共接口引用新创建的对象。是工厂方法的简化版本 工厂方法-定义一个创建对象的接口,但让子类决定实例化哪个类,并通过公共接口引用新创建的对象。 抽象工厂-提供了创建相关对象家族的接口,而无需显式指定它们的类。 null

  • 问题内容: 首先,如果这是一个非常愚蠢的问题,请原谅我,我只是想学习这种语言的核心。我正在阅读《有效的Java》,并且第一章讨论了静态工厂方法与构造方法。他们的利弊。令我困惑的几件事是: 静态工厂方法返回的对象的类是非公共的 -究竟是什么意思? 与构造函数不同,每次调用静态工厂方法都不需要创建新对象 -这是怎么发生的?我仅调用工厂方法来获取新对象,是否将检查方法放入工厂方法中以检查对象是否已存在?

  • 问题内容: 如果我正在编写用于创建对象的静态工厂方法,那么如何为该工厂类使用’@Component’批注并指示(带有一些批注)创建该类bean所应使用的静态工厂方法?以下是我的意思的伪代码: 问题答案: 恐怕您目前无法执行此操作。但是,使用Java配置非常简单: 在这种情况下,不需要任何Spring注释。当然,您可以改用优质的XML。

  • 我正在学习新的设计模式 我编写了一个简单的工厂类,如下所示 我们创建Factory类,如下所示: 现在,当客户端想要添加名为IceCream的新项目时,他们只需创建名为IceCreamFactory的新工厂并从中创建IceCream,如下所示: 我的理解正确吗?我们在这里满足了开闭原则,但对于每个产品(项目),我们都需要一个工厂类,这不是一个可管理的噩梦吗? 注:我指的是一篇文章https://w

  • 问题内容: 我的应用程序有一个问题,当我登录时,该应用程序崩溃并且出现错误: 我试图弄乱gradle并更改变量。这个问题似乎源于此软件包: 然后,它还引用了以下内容: 这是我的摇篮 关于如何找到纠正错误的方法有任何想法吗? 问题答案: 尝试切换到Java 8兼容性,以对某些库进行适当的除糖处理: 此外,Google JSON API可能更需要。

  • 在Java中是否有一个静态工厂方法来创建队列? 或 但无法找到对队列(或出队列)进行同样操作的方便方法。我找到的唯一解决办法是: