23

BallUserInterfaceFactory適切なジェネリック型を持つユーザー インターフェイスのインスタンスを返したいと思います。エラーが発生する以下の例で立ち往生しています:

バウンドの不一致: タイプ BallUserInterfaceFactory のジェネリック メソッド getBaseballUserInterface(BASEBALL) は、引数 (BALL) には適用されません。推定されたタイプ BALL は、境界付きパラメーターの有効な代替ではありません

public class BallUserInterfaceFactory {
    public static <BALL extends Ball> BallUserInterface<BALL> getUserInterface(BALL ball) {

        if(ball instanceof Baseball){
            return getBaseballUserInterface(ball);
        }
        //Other ball types go here

        //Unable to create a UI for ball
        return null;
    }

    private static <BASEBALL extends Baseball> BaseballUserInterface<BASEBALL> getBaseballUserInterface(BASEBALL ball){
        return new BaseballUserInterface<BASEBALL>(ball);
    }
}

BALL が野球であることを保証できないため、getBaseballUserInterface メソッドの呼び出しでパラメーターの型が一致しないことを理解しています。

getBaseballUserInterface メソッド呼び出しで ball パラメーターをキャストすると、次のエラーが発生します。

BaseballUserInterface<Baseball> 型の不一致: からに変換できませんBallUserInterface<BALL>

私が返すものが同じタイプのボールであることを保証できないからです.

私の質問は、この状況に対処するための戦略は何ですか?

(完全を期すために、この例で必要な他のクラスを次に示します)

public class Ball {

}

public class Baseball extends Ball {

}

public class BallUserInterface <BALL extends Ball> {

    private BALL ball;

    public BallUserInterface(BALL ball){
        this.ball = ball;
    }
}

public class BaseballUserInterface<BASEBALL extends Baseball> extends BallUserInterface<BASEBALL>{

    public BaseballUserInterface(BASEBALL ball) {
        super(ball);
    }

}
4

3 に答える 3

22

これは間違った設計パターンです。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メソッドを定義する必要があります。ビジター パターンが正しく機能するためには、の具体的な実装ごとにこのメソッドも実装する必要があります。実装はまったく同じに見えますが、型システムによって適切なメソッドが確実にディスパッチされます。BallVisitorBall

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
于 2012-09-27T21:47:07.123 に答える
3

これは非常に良い質問です。

あなたは残酷にキャストすることができます

    return (BallUserInterface<BALL>)getBaseballUserInterface((Baseball)ball);

強制するので、答えは理論的に欠陥がありBASEBALL=Baseballます。

消去により動作します。実は消去次第です。

具象化が安全であるというより良い答えがあることを願っています。

于 2012-09-27T20:55:23.377 に答える
0
public class BaseballUserInterface extends BallUserInterface<Baseball> {

    public BaseballUserInterface(Baseball ball) {
        super(ball);
    }
}

ファクトリメソッドの結果としてBallUserInterfaceを使用しています。したがって、どのコンクリートボールが使用されているかを隠すことができます。

public class BallUserInterfaceFactory {

public static BallUserInterface<?> getUserInterface(Ball ball) {

        if(ball instanceof Baseball){
            return getBaseballUserInterface((Baseball)ball);
        }

        return null;
    }

    private static BaseballUserInterface getBaseballUserInterface(Baseball ball){
        return new BaseballUserInterface(ball);
    }
}

クライアントがボールのタイプに興味がある場合は、パラメータとしてコンクリートボールを使用したファクトリメソッドを提供する必要があります。

public static BaseballUserInterface getUserInterface(Baseball ball){
    return new BaseballUserInterface(ball);
}
于 2012-09-28T07:41:11.857 に答える