要回答有关使用 a 的直接问题Map
,您建议的课程将是:
interface Question {} // marker interface, not needed but illustrative
public class MyClass1 implements Question {}
public class MyClass2 implements Question {}
public class TestClass {
public void method1(MyClass1 obj) {
System.out.println("You called the method for MyClass1!");
}
public void method2(MyClass2 obj) {
System.out.println("You called the method for MyClass2!");
}
}
那么你Map
将是:
Map<Class<? extends Question>, Consumer<Question>> map = new HashMap<>();
并像这样填充:
TestClass tester = new TestClass();
map.put(MyClass1.class, o -> tester.method1((MyClass1)o)); // cast needed - see below
map.put(MyClass2.class, o -> tester.method2((MyClass2)o));
并像这样使用:
Question question = new MyClass1();
map.get(question.getClass()).accept(question); // calls method1
以上工作正常,但问题是没有办法将映射键的类型与其值的类型连接起来,即您不能使用泛型正确键入消费者的值,因此使用方法参考:
map.put(MyClass1.class, tester::method1); // compile error
这就是为什么您需要在 lambda 中强制转换对象以绑定到正确的方法。
还有另一个问题。如果有人创建了一个新的 Question 类,您直到运行时才知道Map 中没有该类的条目,并且您必须编写类似的代码if (!map.containsKey(question.getClass())) { // explode }
来处理这种可能性。
但是有一个替代...
还有另一种模式确实为您提供了编译时安全性,这意味着您无需编写任何代码来处理“丢失的条目”。该模式称为双重调度(它是访问者模式的一部分)。
它看起来像这样:
interface Tester {
void consume(MyClass1 obj);
void consume(MyClass2 obj);
}
interface Question {
void accept(Tester tester);
}
public class TestClass implements Tester {
public void consume(MyClass1 obj) {
System.out.println("You called the method for MyClass1!");
}
public void consume(MyClass2 obj) {
System.out.println("You called the method for MyClass2!");
}
}
public class MyClass1 implements Question {
// other fields and methods
public void accept(Tester tester) {
tester.consume(this);
}
}
public class MyClass2 implements Question {
// other fields and methods
public void accept(Tester tester) {
tester.consume(this);
}
}
并使用它:
Tester tester = new TestClass();
Question question = new MyClass1();
question.accept(tester);
或许多问题:
List<Question> questions = Arrays.asList(new MyClass1(), new MyClass2());
questions.forEach(q -> q.accept(tester));
此模式通过将回调放入目标类来工作,目标类可以绑定到为对象处理该类的正确方法this
。
这种模式的好处是,如果创建了另一个 Question 类,则需要实现该accept(Tester)
方法,因此 Question 实现者不会忘记实现对 Tester 的回调,并自动检查 Testers 是否可以处理新的实现,例如
public class MyClass3 implements Question {
public void accept(Tester tester) { // Questions must implement this method
tester.consume(this); // compile error if Tester can't handle MyClass3 objects
}
}
还要注意这两个类如何不相互引用 - 它们只引用interface,因此 Tester 和 Question 实现之间存在完全解耦(这也使得单元测试/模拟更容易)。