我一直在寻找用Java解决这个问题的设计思路。
我正在使用一个图书馆(我无法改变它),对于这个例子,我只是打电话给#34;动物"。它包含一个 Animal 接口和一堆实现;我需要根据动物的实现调用不同的方法:
List<Animal> animals = Service.getAnimals();
for(Animal a : animals) {
process(a);
}
private void process(Animal animal) {
if (animal instanceOf Cat) {
processCat(animal);
} else if (animal instanceOf Dog) {
processDog(animal);
} else {
System.out.println("unsopported animal");
}
}
我目前正在通过反思解决这个问题,这个课程包含所有&#34;处理器&#34;并使用
调用它们String methodName = "process" + Animal getClass().getSimpleName(); //ugh
我正在使用Java 8,我很确定必须有更好的设计来解决这个问题。
感谢任何帮助!
答案 0 :(得分:9)
如果Animal是一个密封类,也就是说,它不是动态可扩展的,并且具有有限的已知数量的子类,那么你在示例中偶然发现的if-instanceof模式是经典的“模式匹配” ”
如果Animal是一个你可以控制的类,那么你可以使用Visitor Pattern直接在Animal上创建一个访问方法。
但是,您声明Animal来自外部库,这限制了您可以采取的方法。
您仍然可以使用访问者模式,让所有代码都负责在单个类中与Animals交互,使用方法重载在运行时解析类型(假设您对泛型没有任何问题)。 /秒>
但实际上这就像if-instanceof方法一样灵活,只会让OO人感觉更好。
因此,采用的方法归结为代码组织,以及对代码库有意义的内容。
老实说,if-instanceof就是我想要的,除非方法/行为的数量开始变得过于复杂。
在这种情况下,我会创建一种注册表,为每种动物类型注册一个处理器。
然后,您可以创建一个简单的类,从注册表中获取Animal类型所需的处理器。
我见过的这种注册表模式在Minecraft中使用了很多,但我不确定它是否在其他地方有记录。
基本上它的使用看起来像这样。
void onApplicationStart(){
Registry registry = new Registry();
registry.register(Cat.class, cat -> catProcessor.process(cat));
registry.register(Dog.class, dog -> dogProcessor.process(dog));
registry.registerFallback(Animal.class, ani -> animalProcessor.process(ani));
}
然后,您可以获取注册表以及执行处理的方法。
void whenNeeded(Animal animal){
Registry registry = fetchRegistrySomehow();
registry.for(animal.getClass()).apply(animal);
}
修改强> 故意丢失注册表的实现,因为确切的行为会有所不同,具体取决于您希望如何进行查找,处理类层次结构,何时以及是否应在某些应用程序启动事件后密封注册表。
答案 1 :(得分:1)
我认为你可以隐藏你接近美丽的糖果包装,例如在许多覆盖方法的情况下使用Enum
或Class hierarchy
。
E.g。这是两个Animal
实现,使用不同的方法名称来获取动物的名称:
class Dog implements Animal {
public String getDogName() {
return "dog";
}
}
class Cat implements Animal {
public String getCatName() {
return "cat";
}
}
然后,您可以为每个实施定义Enum
,Functions
:
enum AnimalRegistry {
DOG(Dog.class, animal -> ((Dog)animal).getDogName()),
CAT(Cat.class, animal -> ((Cat)animal).getCatName());
private final Class<? extends Animal> cls;
private final Function<Animal, String> getName;
AnimalRegistry(Class<? extends Animal> cls, Function<Animal, String> getName) {
this.cls = cls;
this.getName = getName;
}
public final String getName(Animal animal) {
return getName.apply(animal);
}
public static AnimalRegistry parseClass(Animal animal) {
for (AnimalRegistry registry : values())
if (registry.cls == animal.getClass())
return registry;
throw new RuntimeException("Unknown Animal implementation: " + animal.getClass().getSimpleName());
}
}
最后,您的客户端代码可能如下所示:
Animal animal = ...;
String animalName = AnimalRegistry.parseClass(animal).getName(animal);
重复,如果你有例如更多的两种方法实现,Enum
变得不那么舒服;那么你可以切换到类hierarhy并且完全像枚举(不要忘记,在JVM中Enum
中的每个常量都是Enum
接口的不同实现。
P.S。你的方法并不是那么糟糕,在很多情况下它很有用:
private void process(Animal animal) {
if (animal instanceof Cat)
process((Cat)animal);
else if (animal instanceof Dog)
process((Dog)animal);
else
System.out.println("unsopported animal");
}
private void process(Cat cat) {
cat.getCatName();
}
private void process(Dog dog) {
dog.getDogName();
}
答案 2 :(得分:0)
我认为the answer provided by @RyanTheLeach通过基于注册表的方法可以很好地解决您的问题。但是,在对您的问题发表评论时,提出了visitor pattern。我提到了#34;如果您自己创建实例&#34;,并且......目前任何现有答案都没有详细说明。我发现你在这里遇到的问题的微妙变化往往是烦人的频繁。由于在Java中实现基于包装器的访问者模式可能有点令人困惑,我想我会添加有关该特定方法的一些细节。
首先,这个似乎就像它应该非常简单。只需创建一个访问者界面,一个基本类型的包装器,以及一个接受访问者实现的包装器方法。
public interface AnimalVisitor {
public void visit(Animal instance);
public void visit(Cat instance);
public void visit(Dog instance);
// ...
}
public class AnimalWrapper {
public final Animal instance;
// ...
public void acceptVisitor(AnimalVisitor visitor) {
visitor.visit(instance);
}
}
方法重载会照顾其余的,对吧?事实证明,这不会起作用,因为Java determines the method overload to call based on the statically determined type,而不是运行时的实际类型。请注意,这与继承不同,其中dynamic dispatch用于在运行时查找virtual method的具体实现。
也许我们可以用泛型来解决这个问题?
public class AnimalWrapper <T extends Animal> {
public final T instance;
// ...
public void acceptVisitor(AnimalVisitor visitor) {
visitor.visit(instance);
}
}
不。它可能似乎就像对visitor.visit(instance)
的调用应该静态地确定子类型,但实际上泛型的静态类型是其捕获的上限(在这种情况下,{{1} })。
也许我们可以通过利用通常的Java模式来获取runtime type information for generics?
来解决这个问题Animal
不幸的是,由于和以前一样的原因,这也不会起作用; public class AnimalWrapper <T extends Animal> {
public final T instance;
private final Class<T> type;
// ...
public void acceptVisitor(AnimalVisitor visitor) {
visitor.visit(type.cast(instance));
}
}
也是通用的,因此它会静态解析为基本类型Class<T> type
。
那么我们怎样才能做到这一点呢? This answer非常清楚地说明了如何为每个要包装的子类型使用单独的包装类来解决这个问题。如果我们可以在不需要这么多不同的独特包装类的情况下完成这项工作,那会不会很好?你说你正在使用Java 8,所以让我们结合使用lambdas和静态工厂方法来实现所有这些或多或少透明(并且只有一个包装类)。
Animal
public abstract class Animal {
private final String instanceName;
private Animal(String instanceName) {
this.instanceName = instanceName;
}
public abstract String typeName();
public String instanceName() {
return instanceName;
}
public static class Cat extends Animal {
public Cat(String instanceName) {
super(instanceName);
}
@Override
public String typeName() {
return "Cat";
}
}
public static class Dog extends Animal {
public Dog(String instanceName) {
super(instanceName);
}
@Override
public String typeName() {
return "Dog";
}
}
public static class Fox extends Animal {
public Fox(String instanceName) {
super(instanceName);
}
@Override
public String typeName() {
return "Fox";
}
}
}
Specialized implementation Signature: Cat Instance type: Cat Instance name: A normal cat. Specialized implementation Signature: Animal (base type) Instance type: Cat Instance name: A stealthy cat. Default implementation Signature: Dog Instance type: Dog Instance name: A dog (only default support). Specialized implementation Signature: Animal (base type) Instance type: Fox Instance name: A fox (no support).
/*
* The Wrapper Class
*/
public class AnimalWrapper {
private final Animal instance;
private final Consumer<AnimalVisitor> visitResolver;
private AnimalWrapper(Animal instance, Consumer<AnimalVisitor> visitResolver) {
this.instance = instance;
this.visitResolver = visitResolver;
}
public Animal getInstance() {
return instance;
}
public void acceptVisitor(AnimalVisitor visitor) {
visitResolver.accept(visitor);
}
public static AnimalWrapper create(Animal instance) {
final Consumer<AnimalVisitor> visitResolver = visitor -> AnimalVisitor.visitResolver(visitor, instance);
return new AnimalWrapper(instance, visitResolver);
}
public static AnimalWrapper create(Animal.Cat instance) {
final Consumer<AnimalVisitor> visitResolver = visitor -> AnimalVisitor.visitResolver(visitor, instance);
return new AnimalWrapper(instance, visitResolver);
}
public static AnimalWrapper create(Animal.Dog instance) {
final Consumer<AnimalVisitor> visitResolver = visitor -> AnimalVisitor.visitResolver(visitor, instance);
return new AnimalWrapper(instance, visitResolver);
}
public static AnimalWrapper create(Animal.Fox instance) {
final Consumer<AnimalVisitor> visitResolver = visitor -> AnimalVisitor.visitResolver(visitor, instance);
return new AnimalWrapper(instance, visitResolver);
}
}
/*
* The Visitor Interface
*/
public interface AnimalVisitor {
public default void visit(Animal instance) {
printMessage("Default implementation", "Animal (base type)", instance);
}
public static void visitResolver(AnimalVisitor visitor, Animal instance) {
visitor.visit(instance);
}
public default void visit(Animal.Cat instance) {
printMessage("Default implementation", "Cat", instance);
}
public static void visitResolver(AnimalVisitor visitor, Animal.Cat instance) {
visitor.visit(instance);
}
public default void visit(Animal.Dog instance) {
printMessage("Default implementation", "Dog", instance);
}
public static void visitResolver(AnimalVisitor visitor, Animal.Dog instance) {
visitor.visit(instance);
}
public static void printMessage(String implementation, String signature, Animal instance) {
System.out.println();
System.out.println(implementation);
System.out.println("\tSignature: " + signature);
System.out.println("\tInstance type: " + instance.typeName());
System.out.println("\tInstance name: " + instance.instanceName());
}
}
/*
* The Visitor Implementation
*/
public class AnimalVisitorImpl implements AnimalVisitor {
@Override
public void visit(Animal instance) {
AnimalVisitor.printMessage("Specialized implementation", "Animal (base type)", instance);
}
@Override
public void visit(Animal.Cat instance) {
AnimalVisitor.printMessage("Specialized implementation", "Cat", instance);
}
}
/*
* Actual Usage
*/
public static void main(String[] args) {
final List<AnimalWrapper> wrappedAnimals = new ArrayList<>();
wrappedAnimals.add(AnimalWrapper.create(new Animal.Cat("A normal cat.")));
wrappedAnimals.add(AnimalWrapper.create((Animal) new Animal.Cat("A stealthy cat.")));
wrappedAnimals.add(AnimalWrapper.create(new Animal.Dog("A dog (only default support).")));
wrappedAnimals.add(AnimalWrapper.create(new Animal.Fox("A fox (no support).")));
final AnimalVisitor visitor = new AnimalVisitorImpl();
for (AnimalWrapper w : wrappedAnimals)
w.acceptVisitor(visitor);
}
),这种方法就会崩溃,我们发现自己回到了基于检查和转换(或反射)的方法。这就是registry based approach outlined by @RyanTheLeach真正闪耀的原因; Collection<Animal>
返回该特定子类型的所有实例共享的唯一静态类对象,从而允许我们即使在显示基类型实例时也能检索正确的lambda。