[英]Processing an object depending on its implementation of an interface in Java
我一直在寻找设计思路来解决Java中的这个问题。
我正在使用一个库( 我无法改变它 ),对于这个例子,我只是称之为“动物”。 它包含一个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");
}
}
我目前正在通过反射解决这个问题,一个类包含所有“处理器”并使用它来调用它们
String methodName = "process" + Animal getClass().getSimpleName(); //ugh
我正在使用Java 8,我很确定必须有更好的设计来解决这个问题。
任何帮助表示赞赏!
如果Animal是一个密封类,也就是说,它不是动态可扩展的,并且具有有限的已知数量的子类,那么在示例中偶然发现的if-instanceof模式是经典的“模式匹配”。
如果Animal是一个你可以控制的类,那么你可以使用访问者模式直接在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);
}
编辑:故意丢失注册表的实现,因为确切的行为会有所不同,具体取决于您希望如何进行查找,处理类层次结构,何时以及是否应在某些应用程序启动事件后密封注册表。
我认为你可以隐藏你进入漂亮的糖果包装,例如在许多覆盖方法的情况下使用Enum
或Class hierarchy
。
例如,这是两个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);
重复一遍,如果你有更多的2种方法可以实现,那么Enum
使用就不那么舒服了; 然后你可以切换到类hierarhy并完全像枚举(不要忘记,在JVM中, Enum
中的每个常量都是Enum
接口的不同实现)。
PS你的方法并不是那么糟糕,在许多情况下这是非常有用的:
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();
}
我认为@RyanTheLeach提供的答案通过基于注册表的方法很好地解决了你的问题。 但是,在对您的问题的评论中,提出了访问者模式 。 我提到“如果你自己创建实例”,并且......目前任何现有的答案都没有详细说明。 我发现你在这里遇到的问题的微妙变化往往是烦人的频繁。 由于在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确定基于静态确定的类型调用方法重载 ,而不是运行时的实际类型。 请注意,这与继承不同,后者使用动态分派在运行时查找虚方法的具体实现。
也许我们可以用泛型来解决这个问题?
public class AnimalWrapper <T extends Animal> {
public final T instance;
// ...
public void acceptVisitor(AnimalVisitor visitor) {
visitor.visit(instance);
}
}
不。 似乎对visitor.visit(instance)
的调用应该静态地确定子类型,但实际上泛型的静态类型是其捕获的上限(在本例中为Animal
)。
也许我们可以通过利用通常的Java模式获取泛型的运行时类型信息来解决这个问题?
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
也是通用的,因此它静态地解析为basetype Animal
。
那么我们怎样才能做到这一点呢? 这个答案很清楚地说明了如何使用一个单独的包装类来处理你要包装的每个子类型。 如果我们可以在不需要这么多不同的独特包装类的情况下完成它,这不是很好吗? 你说你正在使用Java 8,所以让我们结合使用lambdas和静态工厂方法来实现所有这些或多或少透明(并且只有一个包装类)。
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";
}
}
}
/*
* 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);
}
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).
Collection<Animal>
),这种方法就会崩溃,我们发现自己回到了基于检查和转换(或反射)的方法。 这就是@RyanTheLeach概述的基于注册表的方法真正闪耀的原因; animalInstance.getClass()
返回由该特定子类型的所有实例共享的唯一静态类对象,从而允许我们即使在显示基类型实例时也能检索正确的lambda。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.