の使用に関する直接の質問に答えるには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
そのため、ラムダでオブジェクトをキャストして正しいメソッドにバインドする必要があります。
別の問題もあります。誰かが新しい Question クラスを作成した場合、そのクラスの Map にエントリがないことを実行時まで知ることはできず、その不測の事態を処理するようなコードを作成する必要があります。if (!map.containsKey(question.getClass())) { // explode }
しかし、代替手段があります...
コンパイル時の安全性を確保する別のパターンがあります。これは、「不足しているエントリ」を処理するためのコードを記述する必要がないことを意味します。このパターンはDouble Dispatchと呼ばれます ( Visitorパターンの一部です)。
次のようになります。
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 実装者はテスターへのコールバックの実装を忘れず、テスターが新しい実装を処理できることを自動的にチェックすることです。
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
}
}
また、2 つのクラスが相互に参照しないことにも注意してください。これらはinterfaceのみを参照するため、Tester と Question の実装が完全に切り離されています (これにより、単体テスト/モックも容易になります)。