gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 14:19:48 33 4
gpt4 key购买 nike

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

要求:

  • 第一个字符表示“动物的类型”。所以C可能指abstract class Cat ,和D可能指abstract class Dog .
  • 第二个字符可选地表示“动物的亚型”...除了这些亚型被分组为类别(就类别而言)。所以一个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);
}
}

这一切都很好,它应该快速、干净、正确的代码委托(delegate)等等。现在,如果动物突然产生依赖性怎么办(我正在使用 Guice)?假设我有一个包含动物声音的图书馆,我希望能够执行 animal.speak()调用声音对象的功能被封装在 Animal 中。 .

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

  • 使用MapBinder设置Enum -> Cat子类配对。然后绑定(bind)Map<K, Provider<V>>映射到工厂类,并传递data进入Cat对象作为创建后的方法调用
  • 创建一个 AssistedInject 工厂,并拥有每个枚举的 makeCat方法调用工厂中正确的方法。问题是,我无法将工厂注入(inject)到枚举实例中,Guice 建议不要使用静态注入(inject)。因此,我必须沿着方法链一路传递我的工厂,这似乎违背了目的。而且,这并不能解决不允许错误的字符串调用错误的构造函数的问题。
  • 创建手动工厂对象。虽然我不确定有多少工作应该由工厂完成,有多少工作应该由枚举(如果有的话)完成。

最好的解决方案是什么?

最佳答案

你真的有两个问题:

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

问题 1。

Guice 真的非常想要在启动时使用启动时提供的信息制作一个大的对象图。然而,它的强大之处在于它能够根据运行时的条件改变其行为 - 因此许多基于 Guice 构建的框架都做了一些事情来实现这一点 - Servlet 支持有其请求范围 em> 它允许注入(inject) servlet 请求,等等。

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

  • Assisted Inject
  • Custom scopes
  • 写一篇一次性的Provider<Animal>它以某种方式获取相关数据(通常使用 ThreadLocal - 自定义范围通常是此模式的概括)并创建正确的对象

问题 2。

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

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

是否为枚举

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

您可以做的是拥有一个 Animal 接口(interface),然后是一个实现该接口(interface)的枚举 - 这样您就可以获得接口(interface)的灵 active ,并且可以在常见情况下使用枚举 - 只需编写所有代码都指向接口(interface),而不是枚举。

如果您确实需要限制某些代码仅采用 Animal 的枚举实例,则无需将该代码绑定(bind)到特定枚举即可做到这一点:

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

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

关于java - 使用 Guice、枚举和静态工厂方法设计工厂框架,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17710053/

33 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com