これは間違った設計パターンです。1 つのジェネリック メソッドと if ラダーを使用する代わりに、オーバーロードを使用する必要があります。オーバーロードにより、if ラダーが不要になり、コンパイラーは実行時まで待機するのではなく、正しいメソッドが呼び出されることを確認できます。
例えば。
public class BallUserInterfaceFactory {
public static BallUserInterface<Baseball> getUserInterface(
Baseball ball) {
return new BallUserInterface<Baseball>(ball);
}
public static BallUserInterface<Football> getUserInterface(
Football ball) {
return new BallUserInterface<Football>(ball);
}
}
BallUserInterface
このようにして、コードが適切なボールの を作成できない場合にコンパイル時エラーが発生するという追加の利点も得られます。
if ラダーを回避するには、ダブル ディスパッチと呼ばれる手法を使用できます。本質的に、インスタンスが自分が属するクラスを認識し、適切なファクトリ メソッドを呼び出すという事実を利用します。これが機能Ball
するには、適切な を返すメソッドが必要BallInterface
です。
メソッドを抽象化するか、例外をスローするか null を返すデフォルトの実装を提供できます。Ball と Baseball は次のようになります。
public abstract class Ball<T extends Ball<T>> {
abstract BallUserInterface<T> getBallUserInterface();
}
.
public class Baseball extends Ball<Baseball> {
@Override
BallUserInterface<Baseball> getBallUserInterface() {
return BallUserInterfaceFactory.getUserInterface(this);
}
}
物事をもう少しきれいにするために、getBallUserInterface
パッケージを非公開にして、 で汎用ゲッターを提供することをお勧めしBallUserInterfaceFactory
ます。ファクトリは、null やスローされた例外などの追加のチェックを管理できます。例えば。
public class BallUserInterfaceFactory {
public static BallUserInterface<Baseball> getUserInterface(
Baseball ball) {
return new BallUserInterface<Baseball>(ball);
}
public static <T extends Ball<T>> BallUserInterface<T> getUserInterface(
T ball) {
return ball.getBallUserInterface();
}
}
訪問者パターン
コメントで指摘されているように、上記の問題の 1 つは、Ball
クラスが UI の知識を持っている必要があることです。これは非常に望ましくありません。ただし、ビジター パターンを使用すると、ダブル ディスパッチを使用できるようになりますが、さまざまなBall
クラスと UI を切り離すこともできます。
まず、必要なビジター クラスとファクトリ関数:
public interface Visitor<T> {
public T visit(Baseball ball);
public T visit(Football ball);
}
public class BallUserInterfaceVisitor implements Visitor<BallUserInterface<? extends Ball>> {
@Override
public BallUserInterface<Baseball> visit(Baseball ball) {
// Since we now know the ball type, we can call the appropriate factory function
return BallUserInterfaceFactory.getUserInterface(ball);
}
@Override
public BallUserInterface<Football> visit(Football ball) {
return BallUserInterfaceFactory.getUserInterface(ball);
}
}
public class BallUserInterfaceFactory {
public static BallUserInterface<? extends Ball> getUserInterface(Ball ball) {
return ball.accept(new BallUserInterfaceVisitor());
}
// other factory functions for when concrete ball type is known
}
ビジターとファクトリ関数ではワイルドカードを使用する必要があることに注意してください。これは型安全のために必要です。どのタイプのボールが渡されたかわからないため、メソッドは返される UI (ボール UI 以外) を確認できません。
次に、を受け入れる抽象accept
メソッドを定義する必要があります。ビジター パターンが正しく機能するためには、の具体的な実装ごとにこのメソッドも実装する必要があります。実装はまったく同じに見えますが、型システムによって適切なメソッドが確実にディスパッチされます。Ball
Visitor
Ball
public interface Ball {
public <T> T accept(Visitor<T> visitor);
}
public class Baseball implements Ball {
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this);
}
}
最後に、これらすべてをまとめることができるコードを少し示します。
Ball baseball = new Baseball();
Ball football = new Football();
List<BallUserInterface<? extends Ball>> uiList = new ArrayList<>();
uiList.add(BallUserInterfaceFactory.getUserInterface(baseball));
uiList.add(BallUserInterfaceFactory.getUserInterface(football));
for (BallUserInterface<? extends Ball> ui : uiList) {
System.out.println(ui);
}
// Outputs:
// ui.BaseballUserInterface@37e247e2
// ui.FootballUserInterface@1f2f0ce9