考虑一种情况,我希望将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
中包含其他信息。例如,它可能具有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
对象makeCat
方法在工厂中调用正确的方法。问题是,我不能将工厂注入Enumeration实例,而Guice建议不要使用静态注入。因此,我必须一直沿着方法链传递我的工厂,这似乎打败了目的。此外,这并没有解决不允许错误的String调用错误的构造函数的问题。什么是最好的解决方案?
答案 0 :(得分:1)
你有两个问题,真的:
Guice 真的非常希望使用启动时提供的信息在启动时制作一个大对象图。然而,很多强大的功能来自于它能够根据运行时的条件改变其行为 - 因此很多构建在Guice上的框架都可以实现这一点 - Servlet支持具有请求范围,它允许注入servlet请求,等等。
有三种基本方法可以在创建对象时动态创建对象:
Provider<Animal>
以某种方式获取相关数据(通常使用ThreadLocal
- 自定义范围通常是此模式的概括)并创建正确的对象假设输入是在运行时即时提供的,并且假设您有一个工厂或提供者将创建正确的对象,您需要说服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对象是无状态的,您可能只是迭代所有可能的组合并在启动时创建所有组合,然后您只是进行简单的查找。或者您可以解析输入并即时做出决定 - 取决于您的需要。
我建议不要使用枚举这些东西 - 你会发现迟早你想要实现一个Animal
包装另一个Animal
并委托给它,或类似的东西,你无法即时创建枚举实例。
你可以做的是拥有一个Animal 接口,然后是一个实现该接口的枚举 - 这样你就可以获得接口的灵活性,并且可以使用枚举用于常见的情况 - 只需写您在接口上的所有代码,而不是枚举。
如果你真的,真的需要限制一些代码只采用动画的枚举实例,你可以做到这一点,而不必将代码绑定到特定的枚举:
public <A extends Animal & Enum<A>> void foo(A animal) { ... }
它可以为您提供枚举的所有好处,同时仍然可以编写可以在将来重新用于新枚举的代码。